From 6476aadee52b05cda46b0f971baa1f677e6951e7 Mon Sep 17 00:00:00 2001 From: Maksym Novozhylov Date: Wed, 20 May 2020 20:33:18 +0200 Subject: [PATCH 1/6] Prepare branch for the upcoming version --- .docgen | 1 - .gitignore | 24 - .tests | 1 - .travis.yml | 15 - LICENSE | 176 ---- MANIFEST.in | 1 - Makefile | 110 --- README.rst | 83 -- changelog.rst | 324 -------- conf.py | 200 ----- docs_readme.rst | 30 - generate_docs.sh | 4 - getting_started.rst | 118 --- how_to.rst | 317 -------- index.rst | 25 - license.rst | 33 - reference_docs.rst | 13 - reports/README | 0 requirements.py | 6 - setup.cfg | 1 - setup.py | 92 --- update_docs.sh | 13 - upwork/__init__.py | 26 - upwork/ca_certs_locater.py | 28 - upwork/client.py | 315 -------- upwork/compatibility.py | 13 - upwork/config.py | 12 - upwork/desktop_app.py | 60 -- upwork/exceptions.py | 73 -- upwork/http.py | 53 -- upwork/namespaces.py | 57 -- upwork/oauth.py | 141 ---- upwork/routers/__init__.py | 11 - upwork/routers/finreport.py | 195 ----- upwork/routers/hr.py | 1363 ------------------------------- upwork/routers/job.py | 47 -- upwork/routers/messages.py | 168 ---- upwork/routers/offers.py | 275 ------- upwork/routers/provider.py | 358 --------- upwork/routers/task.py | 471 ----------- upwork/routers/team.py | 221 ----- upwork/routers/timereport.py | 103 --- upwork/tests.py | 1476 ---------------------------------- upwork/utils.py | 164 ---- upwork/web_based_app.py | 67 -- 45 files changed, 7284 deletions(-) delete mode 100644 .docgen delete mode 100644 .gitignore delete mode 100644 .tests delete mode 100644 .travis.yml delete mode 100644 LICENSE delete mode 100644 MANIFEST.in delete mode 100644 Makefile delete mode 100644 README.rst delete mode 100644 changelog.rst delete mode 100644 conf.py delete mode 100644 docs_readme.rst delete mode 100644 generate_docs.sh delete mode 100644 getting_started.rst delete mode 100644 how_to.rst delete mode 100644 index.rst delete mode 100644 license.rst delete mode 100644 reference_docs.rst delete mode 100644 reports/README delete mode 100644 requirements.py delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 update_docs.sh delete mode 100644 upwork/__init__.py delete mode 100644 upwork/ca_certs_locater.py delete mode 100644 upwork/client.py delete mode 100644 upwork/compatibility.py delete mode 100644 upwork/config.py delete mode 100644 upwork/desktop_app.py delete mode 100644 upwork/exceptions.py delete mode 100644 upwork/http.py delete mode 100644 upwork/namespaces.py delete mode 100644 upwork/oauth.py delete mode 100644 upwork/routers/__init__.py delete mode 100644 upwork/routers/finreport.py delete mode 100644 upwork/routers/hr.py delete mode 100644 upwork/routers/job.py delete mode 100644 upwork/routers/messages.py delete mode 100644 upwork/routers/offers.py delete mode 100644 upwork/routers/provider.py delete mode 100644 upwork/routers/task.py delete mode 100644 upwork/routers/team.py delete mode 100644 upwork/routers/timereport.py delete mode 100644 upwork/tests.py delete mode 100644 upwork/utils.py delete mode 100644 upwork/web_based_app.py diff --git a/.docgen b/.docgen deleted file mode 100644 index 2645f53..0000000 --- a/.docgen +++ /dev/null @@ -1 +0,0 @@ -# read docs_readme.rst diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 39fb738..0000000 --- a/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -*.*~ -*.pyc -*.swo -*.swp -*.vim -.project -docs/* -.coverage -.pydevproject -.settings/* -build/* -dist/* -lib/* -src/* -bin/* -include/* -man/* -.env/* -examples/upwork -upwork/__init__.py.back -python_upwork.egg-info -reference-docs -_gh-pages -.idea diff --git a/.tests b/.tests deleted file mode 100644 index 5b110e5..0000000 --- a/.tests +++ /dev/null @@ -1 +0,0 @@ -nosetests diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 95b8fab..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: python - -python: - - "2.7" - - "3.6" - -install: "pip install -r requirements.py" - -script: nosetests - -notifications: - email: - recipients: - - apisupport@upwork.com - on_failure: change diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d9a10c0..0000000 --- a/LICENSE +++ /dev/null @@ -1,176 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index c4bf456..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include README.rst \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 863930e..0000000 --- a/Makefile +++ /dev/null @@ -1,110 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = PYTHONPATH=. sphinx-build -PAPER = -BUILDDIR = docs - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Mailpost.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Mailpost.qhc" - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -project=python-upwork -TMPDIR=/tmp -docs_dir=$(TMPDIR)/$(project)-docs - -ghdocs: - rm -rf $(docs_dir) - $(MAKE) html - cp -r docs/html $(docs_dir) - mv $(docs_dir)/_static $(docs_dir)/static - mv $(docs_dir)/_sources $(docs_dir)/sources - perl -pi -e "s/_sources/sources/g;" $(docs_dir)/*.html - perl -pi -e "s/_static/static/g;" $(docs_dir)/*.html - rm -rf docs - git checkout gh-pages - -rm -r sources static - cp -rf $(docs_dir)/* . - git add . - git commit -a -m 'Updates $(project) documentation.' - git checkout master - rm -rf $(docs_dir) diff --git a/README.rst b/README.rst deleted file mode 100644 index a11cd7c..0000000 --- a/README.rst +++ /dev/null @@ -1,83 +0,0 @@ -.. image:: http://img.shields.io/packagist/l/upwork/php-upwork.svg - :target: http://www.apache.org/licenses/LICENSE-2.0.html - :alt: License - -.. image:: https://badge.fury.io/py/python-upwork.svg - :target: http://badge.fury.io/py/python-upwork - :alt: PyPI version - -.. image:: https://img.shields.io/github/release/upwork/python-upwork.svg - :target: https://github.com/upwork/python-upwork/releases - :alt: GitHub release - -.. image:: https://travis-ci.org/upwork/python-upwork.svg - :target: http://travis-ci.org/upwork/python-upwork - :alt: Build status - -Copyright (c) 2010-2019, Upwork http://www.upwork.com -All rights reserved. - - -============================ -Upwork API -============================ -These are Python (**2, and 3 which is "supported" via unofficial PR #27 and not guaranteed**) bindings for Upwork Public API https://developers.upwork.com/ -You can use the API to build apps that will help you: - -* Manage your distributed team -* Search for contractors and jobs -* Send bulk invitations to interview and make offers -* Work with Messages workflow -* Retrieve Time & Financial information for your company, team and contractors - -The API is the best way to communicate between apps. - - -Requirements -============ -httplib2 -oauth2 -urllib3 - - -Installation -============ - - pip install python-upwork - -All the dependencies will be automatically installed as well. - - -SSL Certificates Note -===================== -Package forces ``httplib2`` to use the OS's certificates file by default. - -If you want to use your own certificates, introduce the following code during initialization:: - - os.environ['HTTPLIB_CA_CERTS_PATH'] = '/path/to/my/ca_certs.txt' - - - -Quick start -============ -First, you need to create an API key for the authorization process here: -https://www.upwork.com/services/api/keys - -Installing **Ipython** interactive shell is very useful for working -with the API. It offers features such as auto complete, history and docstring help display -if you add '?' to the end of the variable/function/class/method among other interesting functionalities. -So we really encourage you to install this shell: ``pip install ipython`` - -To get started, look at the docs http://upwork.github.io/python-upwork/how_to.html -and also look at the ``examples/`` folder to see examples how to -obtain oauth access tokens for web application and desktop application. - - -Useful Links -============ - -* `Git repo `_ -* `Issues `_ -* `Documentation `_ -* `Mailing list `_ -* `Facebook group `_ diff --git a/changelog.rst b/changelog.rst deleted file mode 100644 index 9c117e1..0000000 --- a/changelog.rst +++ /dev/null @@ -1,324 +0,0 @@ -.. _changelog: - - -*************** -Changelog -*************** - -.. _1.3.7: - -Version 1.3.7 -------------- -* Add Room Messages API - -.. _1.3.6: - -Version 1.3.6 -------------- -* Add Specialties API -* Add Skills V2 API - -.. _1.3.5: - -Version 1.3.5 -------------- -* move from urllib3 1.25 to 1.24.2 because of the issue#37 - -.. _1.3.4: - -Version 1.3.4 -------------- -* allow for custom ssl cert paths -* allow specifying custom pool managers - -.. _1.3.3: - -Version 1.3.3 -------------- -* Fix Team_V3.get_workdiaries - parameters were not sent -* Default `timeout` parameter for urllib3 has been set to the maximum 8 seconds -* The `retries` parameter for urllib3 has been set to `False` - -.. _1.3.2: - -Version 1.3.2 -------------- -* Update urllib3 dependency to 1.25 (CVE-2019-11324) - -.. _1.3.1: - -Version 1.3.1 -------------- -* Fix broken get_workdiaries which returned empty response all the time after upgrading related API to V3 - -.. _1.3.0: - -Version 1.3.0 -------------- -* Stop supporting deprecated Teamrooms API -* Migrate Workdiaries, Workdays and Snapshots API to v3 -* Python 3 compatibility PR has been merged - -.. _1.2.3: - -Version 1.2.3 -------------- -* Added support for ``page`` parameter in `List freelancer applications ` -* Fixed issue #20 in :py:meth:` `. - -.. _1.2.2: - -Version 1.2.2 -------------- -* Applications API has moved from v3 to v4 - -.. _1.2.1: - -Version 1.2.1 -------------- -* Bug fixes - -.. _1.2.0: - -Version 1.2.0 -------------- -* Added Messages API (new) -* Message API (V1) is now fully depricated - -.. _1.1.0: - -Version 1.1.0 -------------- -* Get Categories (V1) is now fully depricated -* Added new Activities API - :py:meth:`Assign to specific engagement the list of activities `. - -.. _1.0.2: - -Version 1.0.2 -------------- -* os.path.join changed to urlparse.urljoin for supporting Windows systems -* added timeout option for configuring oauth client -* added paging support for Activities API, (!) note: the changes are not backward compatible for get_team_tasks and get_company_tasks - -.. _1.0.1: - -Version 1.0.1 -------------- -* Added new API call - :py:meth:`Get Workdays by Company `. -* Added new API call - :py:meth:`Get Workdays by Contract `. - -.. _1.0.0: - -Version 1.0.0 -------------- -* Rebranding, the library moved to ``python-upwork`` - -.. _0.5.8: - -Version 0.5.8 -------------- -* Added new API call - :py:meth:`Get Snapshot by Contract `. -* Added new API call - :py:meth:`Update Snapshot memo by Contract `. -* Added new API call - :py:meth:`Delete Snapshot by Contract `. -* Fixed broken API call - :py:meth:`Get Work Diary by Contract `. -* Added support of separate parameter ``related_jobcategory2`` in `Send client offer ` -* Fixed issue with wrong name of ``milestones`` parameter in `Send client offer ` -* Fixed issue with passing ``milestones`` and ``context`` parameters in `Send client offer ` - -.. _0.5.7: - -Version 0.5.7 -------------- -* Added new API call - :py:meth:`Accept or decline an offer `. -* Added new conditionally required parameter ``category2`` to :py:meth:`Post job ` API. - -.. _0.5.6: - -Version 0.5.6 -------------- -* Added new API call - :py:meth:`List categories (v2) `. -* Added new API call - :py:meth:`Get Work Diary by Contract `. -* Recent changes from API Changelog - Wednesday, 2015-01-12 -* Recent changes from API Changelog - Wednesday, 2014-12-03 -* Recent changes from API Changelog - Friday, 2014-11-21 -* Recent changes from API Changelog - Friday, 2014-10-31 - -.. _0.5.5: - -Version 0.5.5.1 ---------------- -Minor maintenance release: - -* Updated urllib3 requirements to ``urllib3==1.10` -* Use fixed requirements in the ``setup.py`` -* Add ``httplib.system-ca-certs-locater`` and update Readme - -.. _0.5.5: - -Version 0.5.5 -------------- -* Added new API call - :py:meth:`Create a new Milestone `. -* Added new API call - :py:meth:`Edit the Milestone `. -* Added new API call - :py:meth:`Approve the Milestone `. -* Added new API call - :py:meth:`Activate the Milestone `. -* Added new API call - :py:meth:`Delete the Milestone `. -* Added new API call - :py:meth:`Submit for Approval `. -* Added new API call - :py:meth:`Approve the Submission `. -* Added new API call - :py:meth:`Reject the Submission `. -* Added new API call - :py:meth:`Get all Submissions for the Milestone `. -* Added new API call - :py:meth:`Get Active Milestone for the Contract `. - -* ``end_date`` parameter in :py:meth:`Post Job ` ad :py:meth:`Update Job ` is deprecated, keyword argument still remains for backwards compatibility - and will be removed in future releases. - -.. _0.5.4: - -Version 0.5.4 -------------- -* Added new API call - :py:meth:`Suspend Contract `. -* Added new API call - :py:meth:`Restart Contract `. -* :py:meth:`Archive `/:py:meth:`unarchive ` activities calls now support a list of codes. - -.. _0.5.3: - -Version 0.5.3 -------------- -* New API calls added: - 1. Added :py:meth:`List activities for specific engagement` via ``task_v2`` router. - 2. Added :py:meth:`Reasons metadata` call. - 3. Added :py:class:`Offers router` with handy number of calls for managing offers as a client and as a freelancer. - 4. Added :py:class:`HR_V3 router` with a number of calls for getting job applications as a client and as a freelancer. - 5. Added :py:meth:`List threads by context ` call. -* Removed mistakenly documented by Upwork but not working API call for getting team adjustments. - -.. _0.5.2: - -Version 0.5.2 -------------- -* Fixed engagements API call, so that you can call - ``client.hr.get_engagements()`` without any parameter - to get all engagements for authorized user. -* oTask API strongly reworked, from now Task Codes are - renamed to Activities and it's behavior is changed: - - 1. Activity now is assigned to the engagement ID. - It will appear it user's Upwork Team Client only if - it was assigned to the user's engagement. - 2. You cannot delete activity. You can archive it - and unarchive if necessary. - 3. Activities are created on the team level, - you can create a company level activities by - passing ``team_id`` that is equal to ``company_id`` - (which is ``parent__team_id``). There's a methods - for this already, please see the reference documentation. - Note that archived activity has empty engagements list, - so if you decide to unarchive an activity, you need to - do an extra update call to assign the activity to someone. - 4. When creating/updating activities you can pass optional - ``engagements`` parameter, that should be a list of engagements - that will be assigned to the Activity. Otherwise the activity - won't be assigned to anyone. If you want to assign created/updated - activity to all engagements in the company, you can set - the ``all_in_company`` parameter. - 5. ``update_batch_tasks`` call is marked as experimental, - use it on your own risk. It will be reworked in future. - -.. _0.5.1: - -Version 0.5.1 -------------- -* Fixed bug preventing update (``PUT`` method) for oTask codes that - contained non-urlsafe characters, e.g. "space", "colon", etc. - -.. _0.5: - -Version 0.5 ------------------ -*October 2013* - -Backwards incompatibility changes: - -* Old key-based authorization is completely removed, now the only way - to authorize is oAuth 1.0 -* ``upwork.Client`` class doesn't support ``auth`` keyword argument any more, - as now there's only one way of doing authorization -* Introduced V2 API calls for - :py:meth:`Search Providers` and - :py:meth:`Search Jobs`. - V1 API calls still work but to the end of 2013 will be switched off. - So we greatly encourage you to use V2 API calls. -* ``examples/`` directory of the repository is updated with new examples for - web and desktop application - -Improvements: - -* Clean up API to be consistent with official Upwork API documentation -* Now we use ``urllib3`` and all Http exceptions returned by API have - meaningful messages -* Real PUT and DELETE json calls -* Some parts of API are fixed with to work correctly. Please refer to the - method's docstring to see comprehensive description - -*Nov 2012* - -* Add Metadata Api -* Fixed job posting issue -* Add advanced logging - - -.. _0.4: - -Version 0.4 ------------------ -*May 2011* - -* *Incompatibility with previous release* Changed name of the otask router to the task -* *Incompatibility with previous release* Chaged name of the oticket router to the ticket ?? -* *Incompatibility with previous release* Changed name of the time_report router to the timereport -* *Incompatibility with previous release* Changed name of the finreports router to the finreport -* *Incompatibility with previous release* "from upwork import \*" now import only: "get_version", "Client", "utils" -* All routers moved from the __init__.py to the own files in the routers dir. -* All helper classes moved to own modules -* Added logging inside exceptions -* Added possiblity to switch off unused routers inside client class -* Added oconomy, finance routers -* Added Upwork oAuth support - -.. _0.2: - -Version 0.2 ------------------ -*October 2010* - -* All helpers classes moved to the utils.py, added Table helper class -* *Incompatibility with previous release* Changed names of the methods' params to reflect real Upwork params - e.g. company_reference vs company name - -.. _0.1.2: - -Version 0.1.2 ------------------ -*29 September 2010* - -Bug fix release - -* Fixed check_token method -* Fixed KeyError on empty workdiaries - -.. _0.1.1: - -Version 0.1.1 ------------------ -*15 July 2010* - -Bug fix release - -* Fixed HR2.get_user_role(user_id=None, team_id=None, sub_teams=False) method to correctly get user roles when both user reference and team reference were submitted - previously only one of them was used in the request -* Documentation fixes - -.. _0.1: - -Version 0.1 ------------------ -*08 July 2010* - -First public release diff --git a/conf.py b/conf.py deleted file mode 100644 index d3eeee7..0000000 --- a/conf.py +++ /dev/null @@ -1,200 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Mailpost documentation build configuration file, created by -# sphinx-quickstart on Tue Apr 27 12:07:50 2010. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# add current path to Python path -sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary', - 'sphinx.ext.doctest', 'sphinx.ext.todo', - 'sphinx.ext.coverage', 'sphinx.ext.pngmath', - 'sphinx.ext.ifconfig'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'python-upwork' -copyright = u'2010-2013, upwork' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = __import__('upwork').get_version()[:2] -# The full version, including alpha/beta/rc tags. -release = __import__('upwork').get_version() - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = [] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'sphinxdoc' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_use_modindex = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'python-upworkhtml' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'python-upwork.tex', u'python-upwork Documentation', - u'upwork', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_use_modindex = True diff --git a/docs_readme.rst b/docs_readme.rst deleted file mode 100644 index 2ab2b76..0000000 --- a/docs_readme.rst +++ /dev/null @@ -1,30 +0,0 @@ -======================== -Generating documentation -======================== - -For documentation we use ``sphinx``, it autogenerates the documentation into html. - -To generate new docs do the following: - -1) Generate reference documetation for ``upwork`` module:: - - sphinx-apidoc --force -o reference-docs upwork - -2) Edit the ``reference-docs/upwork.rst``: - - * Move ``Module contents`` section to the top - * Delete ``upwork.tests module`` section - -3) Edit the ``reference-docs/upwork.routers.rst``: - - * Move ``Module contents`` section to the top - -4) Generate documentation in html format:: - - sphinx-build -b html . _gh-pages - -5) Check the documentation html that everything is okay. - -6) Upload contents of ``_gh-pages`` folder to the Github Pages (see ``gh-pages`` branch):: - - sh update_docs.sh diff --git a/generate_docs.sh b/generate_docs.sh deleted file mode 100644 index a75c7aa..0000000 --- a/generate_docs.sh +++ /dev/null @@ -1,4 +0,0 @@ -# First delete ``reference-docs`` and ``_gh-pages`` folders - -sphinx-apidoc --force -o reference-docs upwork -sphinx-build -a -b html . _gh-pages diff --git a/getting_started.rst b/getting_started.rst deleted file mode 100644 index 9624f88..0000000 --- a/getting_started.rst +++ /dev/null @@ -1,118 +0,0 @@ -.. _getting_started: - - -*************** -Getting started -*************** - -.. -.. _requirements: - -Requirements ------------------ -You need to install oauth2 and urllib3 to run the python-upwork. These packages are installed automatically, so basicly you don't need to install them manually. - -If you want to contribute to python-upwork, you need to istall ``mock`` and ``nosetests``. - -Mock:: - - pip install mock - #or - easy_install mock - -Nosetests:: - - pip install nose - #or - easy_install nose - -.. _install: - -Install ------------------ -On most UNIX-like systems, you’ll probably need to run these commands as root or using sudo. - -To install via pip:: - - pip install python-upwork - -Or via easy_install:: - - easy_install python-upwork - -Or install from source:: - - python setup.py install - -Also, you can retrieve the most recent version of python-upwork from GitHub:: - - git clone git://github.com/upwork/python-upwork.git - -.. _settings: - -Settings ---------------------- - -You will need to use your public and private Upwork API keys:: - - client = upwork.Client('your public key', 'your secret key') - -To get Upwork API keys, please visit the http://www.upwork.com/services/api/keys - -.. _simple_example: - -Simple Example ---------------------- -This example is very easy to follow in Python interactive console, -we also recommend to use improved interactive console - IPython. - -You can see the full code of examples in ``upwork/examples`` folder. - -Here is the simple example if you are using web API keys. - -Initializing the client:: - - client = upwork.Client(public_key, secret_key) - -Now follow the ``authorize_url``:: - - print client.auth.get_authorize_url() - -After you follow this url you'll be redirected to the callback url that you -entered during API keys creation. The ``oauth_verifier`` parameter is passed to the callback -and you need to copy it's value. If you selected "Desktop" type of the key instead -of "Web", the value of oAuth verifier will be just displayed to you. - -Now you get the verifier copied into your buffer and you can get you access token:: - - verifier = raw_input('Enter oauth_verifier: ') - oauth_access_token, oauth_access_token_secret = client.auth.get_access_token(verifier) - -Great! Now you got all the necessary credentials for accessing the Upwork API. -Use obtained ``oauth_access_token`` and ``oauth_access_token_secret`` to intialize -a ready-to-go Client instance:: - - client = upwork.Client(public_key, secret_key, - oauth_access_token=oauth_access_token, - oauth_access_token_secret=oauth_access_token_secret) - -To check it works:: - - print client.auth.get_info() - -This call will give you information about the currently authorized user. - -.. note:: Make sure you securely store the ``oauth_access_token`` and ``oauth_access_token_secret``. - - -So now just start playing with the API, for example you can get your teamrooms:: - - print client.team.get_teamrooms() - -or get your companies:: - - print client.hr.get_companies() - - -See the Reference Documentation for the full list of available API calls -:ref:`reference_docs` diff --git a/how_to.rst b/how_to.rst deleted file mode 100644 index d03e60f..0000000 --- a/how_to.rst +++ /dev/null @@ -1,317 +0,0 @@ -.. _how_to: - - -*************** -How to -*************** - -.. note:: Every method is fully documented in the docstring, so you can refer to the :ref:`reference_docs` or directly view help in the interactive console. - -To get help on the method use:: - - help(client.provider.search_jobs) - -or if you use ``IPython`` just add ``?`` symbol at the end of an object/class/method:: - - client.provider.search_jobs? - -.. -.. _authenticate: - -Authenticate ------------------ - -https://developers.upwork.com/?lang=python#authentication - -To authenticate your web application with the python-upwork, use something similar -to the code below:: - - public_key = raw_input('Please enter public key: > ') - secret_key = raw_input('Please enter secret key: > ') - - #Instantiating a client without an auth token - client = upwork.Client(public_key, secret_key) - - print "Please to this URL (authorize the app if necessary):" - print client.auth.get_authorize_url() - print "After that you should be redirected back to your app URL with " + \ - "additional ?oauth_verifier= parameter" - - verifier = raw_input('Enter oauth_verifier: ') - - oauth_access_token, oauth_access_token_secret = \ - client.auth.get_access_token(verifier) - - # Instantiating a new client, now with a token. - # Not strictly necessary here (could just set `client.oauth_access_token` - # and `client.oauth_access_token_secret`), but typical for web apps, - # which wouldn't probably keep client instances between requests - client = upwork.Client(public_key, secret_key, - oauth_access_token=oauth_access_token, - oauth_access_token_secret=oauth_access_token_secret) - -.. -.. _provider_information: - -Freelancer's information --------------------------- - -https://developers.upwork.com/?lang=python#public-profiles_get-brief-profile-summary -https://developers.upwork.com/?lang=python#public-profiles_get-profile-details - - -.. currentmodule:: upwork.routers.provider.Provider - -To get information about freelancer, use following methods (click the link to see detailed description of parameters): - -:py:meth:`client.provider.get_provider` - -:py:meth:`client.provider.get_provider_brief` - - -Search Freelancers ------------------- - -.. currentmodule:: upwork.routers.provider.Provider_V2 - -To search for a freelancer (https://developers.upwork.com/?lang=python#public-profiles_search-for-freelancers) by the query string, use: - -:py:meth:`client.provider_v2.search_providers` - -Search Jobs ------------ - -To search jobs(https://developers.upwork.com/?lang=python#jobs_search-for-jobs) by the query string, use: - -:py:meth:`client.provider_v2.search_jobs` - - -.. _metadata_information: - -Metadata information ------------------------- - -.. currentmodule:: upwork.routers.provider.Provider - -To get information about available categories, skills, regions and tests use: - -:py:meth:`client.provider.get_categories_metadata` - -:py:meth:`client.provider.get_skills_metadata` - -:py:meth:`client.provider.get_regions_metadata` - -:py:meth:`client.provider.get_tests_metadata` - - -.. -.. _hiring: - -Hiring ------- - -.. currentmodule:: upwork.routers.hr.HR - -Upwork Hiring API https://developers.upwork.com/?lang=python#contracts-and-offers allows to do such tasks as: - -* Create new job posting :py:meth:`client.hr.post_job` - -* Update existing job post :py:meth:`client.hr.update_job` - -* Work with milestones for fixed priced jobs: see https://developers.upwork.com/?lang=python#contracts-and-offers_milestones - -* View existing jobs :py:meth:`client.hr.get_jobs` -.. currentmodule:: upwork.routers.hr.HR_V1 -* Invite to interview :py:meth:`client.hr_v1.invite_to_interview` -.. currentmodule:: upwork.routers.hr.HR -* Make an offer :py:meth:`client.hr.post_offer` - -* View active engagements :py:meth:`client.hr.get_engagements` - -* Pay a bonus to the contractor :py:meth:`client.hr.post_team_adjustment` - -* End the contract with freelancer :py:meth:`client.hr.end_contract` - - -.. -.. _work_with_jobs: - -Work with jobs ----------------------- - -https://developers.upwork.com/?lang=python#jobs_get-job-profile - -.. currentmodule:: upwork.routers.job - -To work with jobs you should use :py:class:`client.job` wrapper:: - - tasks = client.job.get_job_profile(job key) - -Where: - -* job_key - The job key or a list of keys, separated by ``";"``, number of keys per request is limited by 20. You can access profile by job reference number, in that case you can't specify a list of jobs, only one profile per request is available. - -.. -.. _team_information: - -Team's information ----------------------- - -https://developers.upwork.com/?lang=python#companies-and-teams - -.. currentmodule:: upwork.routers.team.Team_V2 - -After authentication, you can get teams' information from client instance you have: - -:py:meth:`client.team_v2.get_teamrooms` - -To get snapshots: - -:py:meth:`client.team_v2.get_snapshots` - -.. currentmodule:: upwork.routers.team.Team - -To get user's workdiaries inside the team: - -:py:meth:`client.team.get_workdiaries` - - -.. -.. _get_messages: - -Trays and messages ------------------------ - -https://developers.upwork.com/?lang=python#messages - -.. currentmodule:: upwork.routers.mc.MC - -Get user's trays (if user not provided, authenticated user will be taken): - -:py:meth:`client.mc.get_trays` - -Get content of the tray: - -:py:meth:`client.mc.get_tray_content` - -Get content of the thread: - -:py:meth:`client.mc.get_thread_content` - - -.. -.. _send_message: - -Send message ----------------------- - -https://developers.upwork.com/?lang=python#messages_send-message - -To send message: - -:py:meth:`client.mc.post_message` - - -.. -.. _get_timereports: - -Get timereports ----------------------- - -.. currentmodule:: upwork.routers.timereport.TimeReport - -https://developers.upwork.com/?lang=python#reports_time-reports-fields - -To get timereports, use, based on the level of the timereports you need: - -:py:meth:`client.timereport.get_provider_report` - -:py:meth:`timereport.get_company_report` - -:py:meth:`timereport.get_agency_report` - -Below is an example how to construct a GDS query:: - - client.timereport.get_provider_report('user1', - upwork.utils.Query(select=upwork.utils.Query.DEFAULT_TIMEREPORT_FIELDS, - where=(upwork.utils.Q('worked_on') <= date.today()) &\ - (upwork.utils.Q('worked_on') > '2010-05-01'))) - - - client.timereport.get_provider_report('user1', - upwork.utils.Query(select=upwork.utils.Query.DEFAULT_TIMEREPORT_FIELDS, - where=(upwork.utils.Q('worked_on') <= date.today()) &\ - (upwork.utils.Q('worked_on') > '2010-05-01')), hours=True) - - client.timereport.get_agency_report('company1', 'agency1', - upwork.utils.Query(select=upwork.utils.Query.DEFAULT_TIMEREPORT_FIELDS, - where=(upwork.utils.Q('worked_on') <= date.today()) &\ - (upwork.utils.Q('worked_on') > '2010-05-01')), hours=True) - - -.. -.. _get_finreports: - -Get finreports ----------------------- - -.. currentmodule:: upwork.routers.finreport - -https://developers.upwork.com/?lang=python#reports_financial-reports-fields - -Financial reports are also using GDS queries. - -Please see :py:class:`Finreports` wrapper - -.. -.. _work_with_tasks: - -Work with Activities --------------------- - -https://developers.upwork.com/?lang=python#activities - -.. currentmodule:: upwork.routers.task - -To work with Activities you should use :py:class:`client.task` wrapper:: - - tasks = client.task.get_team_tasks('your_company_id', 'your_team_id') - -Note, that ``company_id`` here is basically a ``parent_team__id`` value -from the ``hr.get_team()`` API call. - -.. currentmodule:: upwork.routers.task.Task - -Methods to get activities: - -:py:meth:`client.task.get_team_tasks` - -:py:meth:`client.task.get_company_tasks` - -:py:meth:`client.task.get_team_specific_tasks` - -:py:meth:`client.task.get_company_specific_tasks` - -Create and update activities: - -:py:meth:`client.task.post_team_task` - -:py:meth:`client.task.post_company_task` - -:py:meth:`client.task.put_team_task` - -:py:meth:`client.task.put_company_task` - -Archive/unarchive activities: - -:py:meth:`client.task.archive_team_task` - -:py:meth:`client.task.archive_company_task` - -:py:meth:`client.task.unarchive_team_task` - -:py:meth:`client.task.unarchive_company_task` - -Assign activities to the contractor's engagement: - -:py:meth:`client.task.assign_engagement` diff --git a/index.rst b/index.rst deleted file mode 100644 index bebca19..0000000 --- a/index.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. sampledoc documentation master file - -****************************************** -Python bindings to Upwork API - |release| -****************************************** - -.. toctree:: - :maxdepth: 5 - - getting_started.rst - how_to.rst - reference_docs.rst - changelog.rst - license.rst - - -********************* -Useful Links -********************* - -* Git repo: http://github.com/upwork/python-upwork -* Issues: http://github.com/upwork/python-upwork/issues -* Documentation: http://upwork.github.com/python-upwork/ -* Mailing list: http://groups.google.com/group/python-upwork (python-upwork@googlegroups.com) -* Facebook group: http://www.facebook.com/group.php?gid=136364403050710 diff --git a/license.rst b/license.rst deleted file mode 100644 index 4244dc8..0000000 --- a/license.rst +++ /dev/null @@ -1,33 +0,0 @@ -.. _license: - - -*************** -BSD license -*************** - -Copyright (c) 2010-2015, Upwork https://www.upwork.com - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of Upwork nor the names of its contributors may - be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/reference_docs.rst b/reference_docs.rst deleted file mode 100644 index ab40070..0000000 --- a/reference_docs.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. _reference_docs: - - -******************************************** -Reference Documentation: Classes and Methods -******************************************** - -You can also browse the code on Github: https://github.com/upwork/python-upwork - -.. toctree:: - :maxdepth: 4 - - reference-docs/upwork.rst diff --git a/reports/README b/reports/README deleted file mode 100644 index e69de29..0000000 diff --git a/requirements.py b/requirements.py deleted file mode 100644 index 795b946..0000000 --- a/requirements.py +++ /dev/null @@ -1,6 +0,0 @@ -nose -mock -ipython -oauth2==1.9.0.post1 -urllib3==1.24.2 -httplib2==0.10.3 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 8b13789..0000000 --- a/setup.cfg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/setup.py b/setup.py deleted file mode 100644 index ebe2642..0000000 --- a/setup.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) 2010-2014, Upwork http://www.upwork.com -# All rights reserved. -from __future__ import print_function - -import re -import os -from setuptools import setup, find_packages -from distutils.core import Command - -readme = open(os.path.join(os.path.dirname(__file__), 'README.rst')) -README = readme.read() -readme.close() - -VERSION = (1, 3, 7, 0, 0) - - -def get_version(): - version = '{0}.{1}'.format(VERSION[0], VERSION[1]) - if VERSION[2]: - version = '{0}.{1}'.format(version, VERSION[2]) - if VERSION[3:] == ('alpha', 0): - version = '{0} pre-alpha'.format(version) - else: - if VERSION[3] != 0: - version = "{0}.{1}".format(version, VERSION[3]) - if VERSION[4] != 0: - version = '{0} {1}'.format(version, VERSION[4]) - return version - - -def update_init(version): - """Update version number in the ``upwork/__init__.py``. - - """ - print('Updating ``upwork/__init__.py`` to version "{0}"'.format(version)) - # Update 'VERSION' variable in ``upwork/__init__.py`` - with open('upwork/__init__.py', 'r') as f: - init_contents = f.read() - - new_init = re.sub( - '(VERSION = \'[a-zA-Z0-9\.\s]*\')', - 'VERSION = \'{0}\''.format(version), init_contents) - - # Write backup - with open('upwork/__init__.py.back', 'w') as f: - f.write(init_contents) - - # Write new init - with open('upwork/__init__.py', 'w') as f: - f.write(new_init) - - print('OK') - - -class UpdateVersion(Command): - - description = 'update version in ``upwork/__init__.py``' - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - update_init(get_version()) - - -setup(cmdclass={'update_version': UpdateVersion}, - name='python-upwork', - version=get_version(), - description='Python bindings to Upwork API', - long_description=README, - author='Upwork', - author_email='python@upwork.com', - maintainer='Maksym Novozhylov', - maintainer_email='mnovozhilov@upwork.com', - install_requires=['oauth2==1.9.0.post1', 'urllib3==1.24.2'], - packages=find_packages(), - license='BSD', - download_url='http://github.com/upwork/python-upwork', - url='http://upwork.github.com/python-upwork', - classifiers=['Development Status :: 5 - Production/Stable', - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Topic :: Software Development ' - ':: Libraries :: Python Modules', - 'Topic :: Utilities']) diff --git a/update_docs.sh b/update_docs.sh deleted file mode 100644 index cb0c6a6..0000000 --- a/update_docs.sh +++ /dev/null @@ -1,13 +0,0 @@ -# Before don't forget to check generated html docs before update - -rm -r /tmp/_gh-pages -cp -r _gh-pages /tmp/. -mv /tmp/_gh-pages/_static /tmp/_gh-pages/static -mv /tmp/_gh-pages/_sources /tmp/_gh-pages/sources -perl -pi -e "s/_static/static/g;" /tmp/_gh-pages/*.html -perl -pi -e "s/_sources/sources/g;" /tmp/_gh-pages/*.html -perl -pi -e "s/_static/static/g;" /tmp/_gh-pages/reference-docs/*.html -perl -pi -e "s/_sources/sources/g;" /tmp/_gh-pages/reference-docs/*.html -#git checkout gh-pages -f -#cp -rf /tmp/_gh-pages/* . -cp -rf /tmp/_gh-pages/* ../gh-pages diff --git a/upwork/__init__.py b/upwork/__init__.py deleted file mode 100644 index 6778444..0000000 --- a/upwork/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2018 Upwork - -# Updated by the script -"""Main package of the python bindings for Upwork API. - -For convenience some most commonly used functionalities are imported here, -so you can use:: - - from upwork import Client - from upwork import raise_http_error - -""" - -VERSION = '1.3.7' - - -def get_version(): - return VERSION - - -from upwork.client import Client -from upwork.http import raise_http_error - -__all__ = ["get_version", "Client", "raise_http_error"] diff --git a/upwork/ca_certs_locater.py b/upwork/ca_certs_locater.py deleted file mode 100644 index d46c416..0000000 --- a/upwork/ca_certs_locater.py +++ /dev/null @@ -1,28 +0,0 @@ -import os - -LINUX_PATH = '/etc/ssl/certs/ca-certificates.crt' - -# openssl installed using `brew install openssl` -OSX_PATH = '/usr/local/etc/openssl/cert.pem' - -CUSTOM_SSL_CERT_PATH = os.getenv('UPWORK_SSL_CERT', '') - - -def get(): - """Return a path to a certificate authority file. - """ - # FIXME(dhellmann): Assume Linux for now, add more OSes and - # platforms later. - - if os.path.exists(LINUX_PATH): - return LINUX_PATH - - if os.path.exists(OSX_PATH): - return OSX_PATH - - if CUSTOM_SSL_CERT_PATH and os.path.exists(CUSTOM_SSL_CERT_PATH): - return CUSTOM_SSL_CERT_PATH - - # Fall back to the httplib2 default behavior by raising an - # ImportError if we have not found the file. - raise ImportError() diff --git a/upwork/client.py b/upwork/client.py deleted file mode 100644 index d8cffc0..0000000 --- a/upwork/client.py +++ /dev/null @@ -1,315 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork - -import os -import json -import logging -import urllib3 - -from urllib3 import Retry - -from upwork.oauth import OAuth -from upwork.http import raise_http_error -from upwork.utils import decimal_default -from upwork.exceptions import IncorrectJsonResponseError - -__all__ = ["Client"] - -logger = logging.getLogger('python-upwork') - -if os.environ.get("PYTHON_UPWORK_DEBUG", False): - if os.environ.get("PYTHON_UPWORK_DEBUG_FILE", False): - fh = logging.FileHandler(filename=os.environ["PYTHON_UPWORK_DEBUG_FILE"] - ) - fh.setLevel(logging.DEBUG) - logger.addHandler(fh) - else: - ch = logging.StreamHandler() - ch.setLevel(logging.DEBUG) - logger.addHandler(ch) -else: - ch = logging.StreamHandler() - ch.setLevel(logging.CRITICAL) - logger.addHandler(ch) - - -class Client(object): - """ - Main API client with oAuth v1 authorization. - - *Parameters:* - :public_key: Public API key - - :secret_key: API key secret - - :oauth_access_token: oAuth access token public key - - :oauth_access_token_secret: oAuth access token secret key - - :fmt: (optional, default ``json``) - API response format. - Currently only ``'json'`` is supported - - :finreport: (optional, default ``True``) - Whether to attach - :py:mod:`upwork.routers.finreport` router - - :hr: (optional, default ``True``) - Whether to attach - :py:mod:`upwork.routers.hr` router - - :messages: (optional, default ``True``) - Whether to attach - :py:mod:`upwork.routers.messages` router - - :offers: (optional, default ``True``) - Whether to attach - :py:mod:`upwork.routers.offers` router - - :provider: (optional, default ``True``) - Whether to attach - :py:mod:`upwork.routers.provider` router - - :task: (optional, default ``True``) - Whether to attach - :py:mod:`upwork.routers.task` router - - :team: (optional, default ``True``) - Whether to attach - :py:mod:`upwork.routers.team` router - - :timereport: (optional, default ``True``) - Whether to attach - :py:mod:`upwork.routers.timereport` router - - :job: (optional, default ``True``) - Whether to attach - :py:mod:`upwork.routers.job` router - - :timeout: (optional, default ``8 secs``) - Socket operations timeout. - - :poolmanager: (optional, default ``None``) - http connection pool manager - from :py:mod:`urllib3.poolmanager` - """ - - def __init__(self, public_key, secret_key, - oauth_access_token=None, oauth_access_token_secret=None, - fmt='json', finreport=True, hr=True, messages=True, - offers=True, provider=True, task=True, team=True, - timereport=True, job=True, timeout=8, poolmanager=None): - - self.public_key = public_key - self.secret_key = secret_key - self.fmt = fmt - - # Catch the warning about - # """ - # SecurityWarning: Certificate has no `subjectAltName`, - # falling back to check for a `commonName` for now. - # This feature is being removed by major browsers - # and deprecated by RFC 2818. - # (See https://github.com/shazow/urllib3/issues/497 for details.) - # """ - # The warning will appear only in logs - logging.captureWarnings(True) - if poolmanager is None: - from upwork import ca_certs_locater - poolmanager = urllib3.PoolManager( - cert_reqs='CERT_REQUIRED', - ca_certs=ca_certs_locater.get(), - timeout=urllib3.Timeout(connect=0.5, read=float(timeout)), - retries=False - ) - self.http = poolmanager - - self.oauth_access_token = oauth_access_token - self.oauth_access_token_secret = oauth_access_token_secret - - # Namespaces - self.auth = OAuth(self) - - if finreport: - from upwork.routers.finreport import Finreports - self.finreport = Finreports(self) - - if hr: - from upwork.routers.hr import HR_V1, HR, HR_V3, HR_V4 - self.hr_v1 = HR_V1(self) - self.hr = HR(self) - self.hr_v3 = HR_V3(self) - self.hr_v4 = HR_V4(self) - - if messages: - from upwork.routers.messages import Messages - self.messages = Messages(self) - - if offers: - from upwork.routers.offers import Offers - self.offers = Offers(self) - - if provider: - from upwork.routers.provider import Provider, Provider_V2 - self.provider = Provider(self) - self.provider_v2 = Provider_V2(self) - - if task: - from upwork.routers.task import Task, Task_V2 - self.task = Task(self) - self.task_v2 = Task_V2(self) - - if team: - from upwork.routers.team import Team_V3 - self.team_v3 = Team_V3(self) - - if timereport: - from upwork.routers.timereport import TimeReport - self.timereport = TimeReport(self) - - if job: - from upwork.routers.job import Job - self.job = Job(self) - - # Shortcuts for HTTP methods - def get(self, url, data=None): - return self.read(url, data, method='GET', fmt=self.fmt) - - def post(self, url, data=None): - return self.read(url, data, method='POST', fmt=self.fmt) - - def put(self, url, data=None): - return self.read(url, data, method='PUT', fmt=self.fmt) - - def delete(self, url, data=None): - return self.read(url, data, method='DELETE', fmt=self.fmt) - - # The method that actually makes HTTP requests - def urlopen(self, url, data=None, method='GET', headers=None): - """Perform oAuth v1 signed HTTP request. - - *Parameters:* - :url: Target url - - :data: Dictionary with parameters - - :method: (optional, default ``GET``) - HTTP method, possible values: - * ``GET`` - * ``POST`` - * ``PUT`` - * ``DELETE`` - - :headers: (optional, default ``{}``) - Dictionary with header values - - """ - - if headers is None: - headers = {} - - self.last_method = method - self.last_url = url - self.last_data = data - - # TODO: Headers are not supported fully yet - # instead we pass oauth parameters in querystring - if method in ('PUT', 'DELETE'): - post_data = self.auth.get_oauth_params( - url, self.oauth_access_token, - self.oauth_access_token_secret, - {}, method) # don't need parameters in url - else: - if data is None: - data = {} - post_data = self.auth.get_oauth_params( - url, self.oauth_access_token, - self.oauth_access_token_secret, - data, method) - - if method == 'GET': - url = '{0}?{1}'.format(url, post_data) - return self.http.urlopen(method, url) - elif method == 'POST': - return self.http.urlopen( - method, url, body=post_data, - headers={'Content-Type': - 'application/x-www-form-urlencoded;charset=UTF-8'}) - elif method in ('PUT', 'DELETE'): - url = '{0}?{1}'.format(url, post_data) - headers['Content-Type'] = 'application/json' - if data is not None: - data_json = json.dumps(data) - else: - data_json = '' - return self.http.urlopen( - method, url, body=data_json, headers=headers) - - else: - raise Exception('Wrong http method: {0}. Supported' - 'methods are: ' - 'GET, POST, PUT, DELETE'.format(method)) - - def read(self, url, data=None, method='GET', fmt='json'): - """ - Returns parsed Python object or raises an error. - - *Parameters:* - :url: Target url - - :data: Dictionary with parameters - - :method: (optional, default ``GET``) - HTTP method, possible values: - * ``GET`` - * ``POST`` - * ``PUT`` - * ``DELETE`` - - :fmt: (optional, default ``json``) - API response format. - Currently only ``'json'`` is supported - - """ - assert fmt == 'json', "Only JSON format is supported at the moment" - - if '/gds/' not in url: - url = '{0}.{1}'.format(url, fmt) - - logger = logging.getLogger('python-upwork') - - logger.debug('Prepairing to make Upwork call') - logger.debug('URL: {0}'.format(url)) - try: - logger.debug('Data: {0}'.format( - json.dumps(data, default=decimal_default))) - except TypeError: - logger.debug('Data: {0}'.format(str(data))) - logger.debug('Method: {0}'.format(method)) - response = self.urlopen(url, data, method) - - if response.status != 200: - logger.debug('Error: {0}'.format(response)) - raise_http_error(url, response) - - result = response.data - logger.debug('Response: {0}'.format(result)) - - if fmt == 'json': - try: - result = json.loads(result) - except ValueError: - # Not a valid json string - logger.debug('Response is not a valid json string') - raise IncorrectJsonResponseError( - json.dumps({'status': response.status, 'body': result}, - default=decimal_default) - ) - return result - - -if __name__ == "__main__": - import doctest - - doctest.testmod() diff --git a/upwork/compatibility.py b/upwork/compatibility.py deleted file mode 100644 index 85ab259..0000000 --- a/upwork/compatibility.py +++ /dev/null @@ -1,13 +0,0 @@ -import sys - - -if sys.version_info > (3, 0, 0): - from urllib import parse as urlparse - from urllib.parse import quote, urlencode - from urllib.error import HTTPError - from http import client as httplib -else: - import urlparse - import httplib - from urllib import quote, urlencode - from urllib2 import HTTPError diff --git a/upwork/config.py b/upwork/config.py deleted file mode 100644 index 8f47252..0000000 --- a/upwork/config.py +++ /dev/null @@ -1,12 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork -"""Here we watch the ``PYTHON_UPWORK_BASE_URL`` -variable and if it is defined, use it as ``BASE_URL``. - -""" - -import os - -BASE_URL = os.environ.get('PYTHON_UPWORK_BASE_URL', - 'https://www.upwork.com') diff --git a/upwork/desktop_app.py b/upwork/desktop_app.py deleted file mode 100644 index 095abde..0000000 --- a/upwork/desktop_app.py +++ /dev/null @@ -1,60 +0,0 @@ -from __future__ import print_function - -import upwork -from pprint import pprint - -try: - input = raw_input -except NameError: - pass - -def desktop_app(): - """Emulation of desktop app. - Your keys should be created with project type "Desktop". - - Returns: ``upwork.Client`` instance ready to work. - - """ - print("Emulating desktop app") - - public_key = input('Please enter public key: > ') - secret_key = input('Please enter secret key: > ') - - client = upwork.Client(public_key, secret_key) - verifier = input( - 'Please enter the verification code you get ' - 'following this link:\n{0}\n\n> '.format( - client.auth.get_authorize_url())) - - print('Retrieving keys.... ') - access_token, access_token_secret = client.auth.get_access_token(verifier) - print('OK') - - # For further use you can store ``access_toket`` and - # ``access_token_secret`` somewhere - client = upwork.Client(public_key, secret_key, - oauth_access_token=access_token, - oauth_access_token_secret=access_token_secret) - return client - - -if __name__ == '__main__': - client = desktop_app() - - try: - print("My info") - pprint(client.auth.get_info()) - print("Team rooms:") - pprint(client.team.get_teamrooms()) - #HRv2 API - print("HR: companies") - pprint(client.hr.get_companies()) - print("HR: teams") - pprint(client.hr.get_teams()) - print("HR: userroles") - pprint(client.hr.get_user_roles()) - print("Get jobs") - pprint(client.provider.search_jobs({'q': 'python'})) - except Exception as e: - print("Exception at %s %s" % (client.last_method, client.last_url)) - raise e diff --git a/upwork/exceptions.py b/upwork/exceptions.py deleted file mode 100644 index c2087a2..0000000 --- a/upwork/exceptions.py +++ /dev/null @@ -1,73 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork - -import logging -from upwork.compatibility import HTTPError - - -class BaseException(Exception): - """Base exception class. - - Performs logging. - - """ - def __init__(self, *args, **kwargs): - self.upwork_debug(*args, **kwargs) - - def upwork_debug(self, *args, **kwargs): - logger = logging.getLogger('python-upwork') - try: - convert = unicode - except NameError: - convert = str - logger.debug('{0}: {1}'.format( - self.__class__.__name__, - ', '.join(map(convert, args)))) - - -class BaseHttpException(HTTPError, BaseException): - - def __init__(self, *args, **kwargs): - self.upwork_debug(*args, **kwargs) - super(BaseHttpException, self).__init__(*args, **kwargs) - - -class HTTP400BadRequestError(BaseHttpException): - pass - - -class HTTP401UnauthorizedError(BaseHttpException): - pass - - -class HTTP403ForbiddenError(BaseHttpException): - pass - - -class HTTP404NotFoundError(BaseHttpException): - pass - - -class InvalidConfiguredException(BaseException): - pass - - -class APINotImplementedException(BaseException): - pass - - -class AuthenticationError(BaseException): - pass - - -class NotAuthenticatedError(BaseException): - pass - - -class ApiValueError(BaseException): - pass - - -class IncorrectJsonResponseError(BaseException): - pass diff --git a/upwork/http.py b/upwork/http.py deleted file mode 100644 index f6667aa..0000000 --- a/upwork/http.py +++ /dev/null @@ -1,53 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork - -import logging -from .compatibility import HTTPError, httplib - -from upwork.exceptions import HTTP400BadRequestError, HTTP401UnauthorizedError,\ - HTTP403ForbiddenError, HTTP404NotFoundError - -UPWORK_ERROR_CODE = 'x-upwork-error-code' -UPWORK_ERROR_MESSAGE = 'x-upwork-error-message' - - -__all__ = ['raise_http_error'] - - -def raise_http_error(url, response): - """Raise custom ``urllib2.HTTPError`` exception. - - *Parameters:* - :url: Url that caused an error - - :response: ``urllib3`` response object - - """ - status_code = response.status - - headers = response.getheaders() - upwork_error_code = headers.get(UPWORK_ERROR_CODE, 'N/A') - upwork_error_message = headers.get(UPWORK_ERROR_MESSAGE, 'N/A') - - formatted_msg = 'Code {0}: {1}'.format(upwork_error_code, - upwork_error_message) - - if status_code == httplib.BAD_REQUEST: - raise HTTP400BadRequestError(url, status_code, formatted_msg, - headers, None) - elif status_code == httplib.UNAUTHORIZED: - raise HTTP401UnauthorizedError(url, status_code, formatted_msg, - headers, None) - elif status_code == httplib.FORBIDDEN: - raise HTTP403ForbiddenError(url, status_code, formatted_msg, - headers, None) - elif status_code == httplib.NOT_FOUND: - raise HTTP404NotFoundError(url, status_code, formatted_msg, - headers, None) - else: - error = HTTPError(url, status_code, formatted_msg, - headers, None) - logger = logging.getLogger('python-upwork') - logger.debug(str(error)) - raise error diff --git a/upwork/namespaces.py b/upwork/namespaces.py deleted file mode 100644 index 797bfa9..0000000 --- a/upwork/namespaces.py +++ /dev/null @@ -1,57 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork -from .compatibility import urlparse -from .config import BASE_URL - -__all__ = ['Namespace', 'GdsNamespace'] - - -class Namespace(object): - """ - A special 'proxy' class to keep API methods organized. - - Use this class for defining new routers. - - """ - - base_url = urlparse.urljoin(BASE_URL, 'api/') - api_url = None - version = 1 - - def __init__(self, client): - self.client = client - - def full_url(self, url): - """ - Gets relative URL of API method and returns a full URL - """ - return "{0}{1}v{2}/{3}".format(self.base_url, - self.api_url, self.version, url) - - #Proxied client's methods - def get(self, url, data=None): - return self.client.get(self.full_url(url), data) - - def post(self, url, data=None): - return self.client.post(self.full_url(url), data) - - def put(self, url, data=None): - return self.client.put(self.full_url(url), data) - - def delete(self, url, data=None): - return self.client.delete(self.full_url(url), data) - - -class GdsNamespace(Namespace): - """Gds API only allows GET requests.""" - base_url = urlparse.urljoin(BASE_URL, 'gds/') - - def post(self, url, data=None): - return None - - def put(self, url, data=None): - return None - - def delete(self, url, data=None): - return None diff --git a/upwork/oauth.py b/upwork/oauth.py deleted file mode 100644 index 38a261f..0000000 --- a/upwork/oauth.py +++ /dev/null @@ -1,141 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork - -import time -import oauth2 as oauth -import logging - -from .compatibility import urlparse, urlencode -from .config import BASE_URL - - -from upwork.namespaces import Namespace - - -class OAuth(Namespace): - - """Authorization router. - - Has methods for retrieving access tokens and - :py:meth:`~upwork.oauth.OAuth.get_info` method for - checking you're authorized successfully and ready to work with API. - """ - - api_url = 'auth/' - version = 1 - - request_token_url = urlparse.urljoin( - BASE_URL, 'api/auth/v1/oauth/token/request') - authorize_url = urlparse.urljoin(BASE_URL, 'services/api/auth') - access_token_url = urlparse.urljoin(BASE_URL, 'api/auth/v1/oauth/token/access') - - def get_oauth_params(self, url, key, secret, data=None, method='GET', - to_header=False, to_dict=False): - """ - Converts a mapping object to signed url query. - - *Parameters:* - :url: Target url - - :key: Public API key - - :secret: Public API key secret - - :data: Dictionary with data parameters - - :method: Mehtod to be called, default is ``GET`` - - :to_header: If ``True``, data will be encoded as auth - headers - - """ - # Temporary not use incoming data, just generate headers - if data is None: - data = {} - else: - data = data.copy() - - token = oauth.Token(key, secret) - consumer = self.get_oauth_consumer() - data.update({ - 'oauth_token': token.key, - 'oauth_consumer_key': consumer.key, - 'oauth_version': '1.0', - 'oauth_nonce': oauth.generate_nonce(), - 'oauth_timestamp': int(time.time()), - }) - request = oauth.Request(method=method, url=url, parameters=data) - signature_method = oauth.SignatureMethod_HMAC_SHA1() - request.sign_request(signature_method, consumer, token) - - if to_header: - return request.to_header() - - return request.to_postdata() - - def get_oauth_consumer(self): - """ - Returns OAuth consumer object. - """ - return oauth.Consumer(self.client.public_key, self.client.secret_key) - - def get_request_token(self): - """ - Returns request token and request token secret. - """ - client = oauth.Client(self.get_oauth_consumer()) - response, content = client.request(self.request_token_url, 'POST') - if response.get('status') != '200': - raise Exception( - "Invalid request token response: {0}.".format(content)) - request_token = dict(urlparse.parse_qsl(content)) - self.request_token = request_token.get('oauth_token') - self.request_token_secret = request_token.get('oauth_token_secret') - return self.request_token, self.request_token_secret - - def get_authorize_url(self, callback_url=None): - """ - Returns authentication URL to be used in a browser. - """ - oauth_token = getattr(self, 'request_token', None) or\ - self.get_request_token()[0] - if callback_url: - params = urlencode({'oauth_token': oauth_token,\ - 'oauth_callback': callback_url}) - else: - params = urlencode({'oauth_token': oauth_token}) - return '{0}?{1}'.format(self.authorize_url, params) - - def get_access_token(self, verifier): - """ - Returns access token and access token secret. - """ - try: - request_token = self.request_token - request_token_secret = self.request_token_secret - except AttributeError as e: - logger = logging.getLogger('python-upwork') - logger.debug(e) - raise Exception("At first you need to call get_authorize_url") - token = oauth.Token(request_token, request_token_secret) - token.set_verifier(verifier) - client = oauth.Client(self.get_oauth_consumer(), token) - response, content = client.request(self.access_token_url, 'POST') - if response.get('status') != '200': - raise Exception( - "Invalid access token response: {0}.".format(content)) - access_token = dict(urlparse.parse_qsl(content)) - self.access_token = access_token.get('oauth_token') - self.access_token_secret = access_token.get('oauth_token_secret') - return self.access_token, self.access_token_secret - - def get_info(self): - """ - Get a detailed info about current authnticated user \ - and some data from his profile. - - """ - url = 'info' - result = self.get(url) - return result diff --git a/upwork/routers/__init__.py b/upwork/routers/__init__.py deleted file mode 100644 index ba954d8..0000000 --- a/upwork/routers/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork -"""Routers package. - -All communication with Upwork API is handled by the routers. - -When you import ``upwork`` package and instantiate ``upwork.Client`` class, -routers are dynamically attatched to your ``client`` object. - -""" diff --git a/upwork/routers/finreport.py b/upwork/routers/finreport.py deleted file mode 100644 index d423fdc..0000000 --- a/upwork/routers/finreport.py +++ /dev/null @@ -1,195 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork - -from upwork.namespaces import GdsNamespace - - -class Finreports(GdsNamespace): - api_url = 'finreports/' - version = 2 - - def get_provider_billings(self, provider_id, query): - """ - Generate Billing Reports for a Specific Provider. - - *Parameters:* - :provider_id: Provider ID - - :query: The GDS query string - - """ - url = 'providers/{0}/billings'.format(provider_id) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result - - def get_provider_teams_billings(self, provider_team_id, query): - """ - Generate Billing Reports for a Specific Provider's Team. - The authenticated user must be an admin or - a staffing manager of the team. - - *Parameters:* - :provider_team_id: Provider's Team ID - - :query: The GDS query string - - """ - url = 'provider_teams/{0}/billings'.format(provider_team_id) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result - - def get_provider_companies_billings(self, provider_company_id, query): - """ - Generate Billing Reports for a Specific Provider's Company. - The authenticated user must be the company owner - - *Parameters:* - :provider_company_id: Provider's Company ID - - :query: The GDS query string - - """ - url = 'provider_companies/{0}/billings'.format(provider_company_id) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result - - def get_provider_earnings(self, provider_id, query): - """ - Generate Earning Reports for a Specific Provider - - *Parameters:* - :provider_id: Provider ID - - :query: The GDS query string - - """ - url = 'providers/{0}/earnings'.format(provider_id) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result - - def get_provider_teams_earnings(self, provider_team_id, query): - """ - Generate Earning Reports for a Specific Provider's Team. - - *Parameters:* - :provider_team_id: Provider's Team ID - - :query: The GDS query string - - """ - url = 'provider_teams/{0}/earnings'.format(provider_team_id) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result - - def get_provider_companies_earnings(self, provider_company_id, query): - """ - Generate Earning Reports for a Specific Provider's Company. - - *Parameters:* - :provider_company_id: Provider's Team ID - - :query: The GDS query string - - """ - url = 'provider_companies/{0}/earnings'.format(provider_company_id) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result - - def get_buyer_teams_billings(self, buyer_team_id, query): - """ - Generate Billing Reports for a Specific Buyer's Team. - The authenticated user must be an admin or - a staffing manager of the team. - - *Parameters:* - :buyer_team_id: Buyer's Team ID - - :query: The GDS query string - - """ - url = 'buyer_teams/{0}/billings'.format(buyer_team_id) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result - - def get_buyer_companies_billings(self, buyer_company_id, query): - """ - Generate Billing Reports for a Specific Buyer's Company. - The authenticated user must be the company owner. - - *Parameters:* - :buyer_company_id: Buyer's Company ID - - :query: The GDS query string - """ - url = 'buyer_companies/{0}/billings'.format(buyer_company_id) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result - - def get_buyer_teams_earnings(self, buyer_team_id, query): - """ - Generate Earning Reports for a Specific Buyer's Team. - - *Parameters:* - :buyer_team_id: Buyer's Team ID - - :query: The GDS query string - - """ - url = 'buyer_teams/{0}/earnings'.format(buyer_team_id) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result - - def get_buyer_companies_earnings(self, buyer_company_id, query): - """ - Generate Earning Reports for a Specific Buyer's Company. - - *Parameters:* - :buyer_company_id: Buyer's Team ID - - :query: The GDS query string - - """ - url = 'buyer_companies/{0}/earnings'.format(buyer_company_id) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result - - def get_financial_entities(self, accounting_id, query): - """ - Generate Financial Reports for a Specific Account. - - *Parameters:* - :accounting_id: ID of an Accounting entity - - :query: The GDS query string - - """ - url = 'financial_accounts/{0}'.format(accounting_id) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result - - def get_financial_entities_provider(self, provider_id, query): - """ - Generate Financial Reports for an owned Account. - - *Parameters:* - :provider_id: Provider ID - - :query: The GDS query string - - """ - url = 'financial_account_owner/{0}'.format(provider_id) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result diff --git a/upwork/routers/hr.py b/upwork/routers/hr.py deleted file mode 100644 index f385a76..0000000 --- a/upwork/routers/hr.py +++ /dev/null @@ -1,1363 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork - -from upwork.namespaces import Namespace -from upwork.utils import assert_parameter, ApiValueError - - -class HR_V1(Namespace): - """ - HR API version 1 - """ - api_url = 'hr/' - version = 1 - - def invite_to_interview(self, job_id, cover, profile_key=None, - provider_reference=None): - """ - Invite to an interview. - - *Parameters:* - :job_id: Job reference ID - - :cover: Text of the cover letter - - :profile_key: (optional) Unique contractor's key, - e.g. ~~677961dcd7f65c01 - - :provider_reference: (optional) Developer's unique reference ID, - e.g. 12345. Use it if no profile_key available - - Either one of the parameters ``profile_key`` or ``provider_reference`` - should be provided, otherwise error will be raised. - - """ - data = {} - - if profile_key is None and provider_reference is None: - raise ApiValueError('Either one of the parameters ``profile_key`` ' - 'or ``provider_reference`` should be provided') - - if profile_key: - data['profile_key'] = profile_key - - if provider_reference: - data['provider__reference'] = provider_reference - - data['cover'] = cover - - url = 'jobs/{0}/candidates'.format(job_id) - return self.post(url, data) - - -class HR(Namespace): - """ - HR API version 2 - """ - api_url = 'hr/' - version = 2 - - JOB_TYPES = ('hourly', 'fixed-price') - JOB_VISIBILITY_OPTIONS = ('public', 'private', 'upwork', 'invite-only') - JOB_STATUSES = ('open', 'filled', 'cancelled') - JOB_KEEP_OPEN_OPTIONS = ('yes', 'no') - CONTRACT_REASON_OPTIONS = ( - 'API_REAS_MISREPRESENTED_SKILLS', - 'API_REAS_CONTRACTOR_NOT_RESPONSIVE', - 'API_REAS_HIRED_DIFFERENT', - 'API_REAS_JOB_COMPLETED_SUCCESSFULLY', - 'API_REAS_WORK_NOT_NEEDED', - 'API_REAS_UNPROFESSIONAL_CONDUCT', - ) - CONTRACT_WOULD_HIRE_AGAIN_OPTONS = ('yes', 'no') - - """userrole api""" - - def get_user_roles(self): - """ - Retrieve UserRoles object. - - This is a **very important and useful API call**, - it returns a complete list of privileges the currently - authorized user has within all the teams and companies - they have access to. - - """ - url = 'userroles' - result = self.get(url) - return result.get('userroles', result) - - """user api""" - - def get_user(self, user_reference): - """ - Retrieve the user object from the user reference. - - *Parameters:* - :user_reference: The user reference - - """ - url = 'users/{0}'.format(user_reference) - result = self.get(url) - return result.get('user', result) - - def get_user_me(self): - """ - Retrieve currently authenticated user object. - - """ - url = 'users/me' - result = self.get(url) - return result.get('user', result) - - """company api""" - - def get_companies(self): - """ - Retrieve the list of companies to which the current authorized user \ - has access. - - """ - url = 'companies' - result = self.get(url) - return result['companies'] - - def get_company(self, company_referece): - """ - Retrieve the company object from the company reference. - - *Parameters:* - :company_reference: The company reference (can be found using - get_companies method) - - """ - url = 'companies/{0}'.format(company_referece) - result = self.get(url) - return result.get('company', result) - - def get_company_teams(self, company_referece): - """ - Retrieve a list of teams within the company being referenced. - - User has to have access to the referenced company. - - *Parameters:* - :company_reference: The company reference (can be found using - get_companies method) - - """ - url = 'companies/{0}/teams'.format(company_referece) - result = self.get(url) - return result.get('teams', result) - - def get_company_users(self, company_referece, active=True): - """ - Retrieve a list of all users within the referenced company. - - Only available for users with hiring privileges for the company. - - *Parameters:* - :company_reference: The company reference (can be found using - get_companies method) - - :active: ``True``/``False`` (default ``True``) - - """ - url = 'companies/{0}/users'.format(company_referece) - if active: - data = {'status_in_company': 'active'} - else: - data = {'status_in_company': 'inactive'} - result = self.get(url, data) - return result.get('users', result) - - """team api""" - - def post_team_adjustment(self, team_reference, engagement_reference, - comments, charge_amount, notes=None): - """ - Add bonus to an engagement. - - *Parameters:* - :team_reference: The Team reference ID - - :engagement_reference: The Engagement reference ID - - :comments: Comments about this adjustment, e.g. - "Bonus for a good job" - - :charge_amount: The amount that will be charged to the employer, e.g. 110 - - :notes: (optional) Notes - - .. note:: You should use either ``amount`` parameter or \ - ``charge_amount``, but not both. Make sure that at least \ - one of them is present. - - """ - url = 'teams/{0}/adjustments'.format(team_reference) - data = {} - - data['engagement__reference'] = engagement_reference - data['comments'] = comments - - if charge_amount is None or charge_amount == 0: - raise ApiValueError('Missed obligatory parameter ``charge_amount``') - - data['charge_amount'] = charge_amount - - if notes: - data['notes'] = notes - - result = self.post(url, data) - return result.get('adjustment', result) - - def get_teams(self): - """ - Retrieve a list of all the teams that a user has access to. - - This will return teams across all companies the current - user has access to. - - """ - url = 'teams' - result = self.get(url) - return result.get('teams', result) - - def get_team(self, team_reference, include_users=False): - """ - Retrieve the team information. - - *Parameters:* - :team_reference: The team reference - - :include_users: Whether to include details of users - (default: False) - - """ - url = 'teams/{0}'.format(team_reference) - result = self.get(url, {'include_users': include_users}) - #TODO: check how included users returned - return result.get('team', result) - - def get_team_users(self, team_reference, active=True): - """ - Retrieve users of the team. - - *Parameters:* - :team_reference: The team reference - - :active: Status of the users, If ``True`` - return - active users, if ``False`` - return inactive users. - Default value is ``True``. - - """ - url = 'teams/{0}/users'.format(team_reference) - if active: - data = {'status_in_team': 'active'} - else: - data = {'status_in_team': 'inactive'} - result = self.get(url, data) - return result.get('users', result) - - """job api""" - - def get_jobs(self, buyer_team_reference, - include_sub_teams=False, - status=None, created_by=None, created_time_from=None, - created_time_to=None, page_offset=0, page_size=20, - order_by=None): - """ - Retrieves all jobs that a user has manage_recruiting access to. - This API call can be used to find the reference ID of a specific job. - - *Parameters:* - :buyer_team_reference: The buyer's team reference ID - - :include_sub_teams: (optional) <1|0> Whether to include sub-teams - - :status: (optional) Status of a job - - :created_by: (optional) Creator's user_id - - :created_time_from: (optional) timestamp, \ - e.g. 2009-01-20T00:00:01 - - :created_time_to: (optional) timestamp, \ - e.g. 2009-02-20T11:59:59 - - :page_offset: (optional) Number of entries to skip - - :page_size: (optional: default 20) Page size in number - of entries - - :order_by: (optional) - - """ - url = 'jobs' - - data = {} - data['buyer_team__reference'] = buyer_team_reference - - data['include_sub_teams'] = False - if include_sub_teams: - data['include_sub_teams'] = include_sub_teams - - if status: - data['status'] = status - - if created_by: - data['created_by'] = created_by - - if created_time_from: - data['created_time_from'] = created_time_from - - if created_time_to: - data['created_time_to'] = created_time_to - - data['page'] = '{0};{1}'.format(page_offset, page_size) - - if order_by is not None: - data['order_by'] = order_by - - result = self.get(url, data) - return result.get('jobs', result) - - def get_job(self, job_reference): - """ - Retrieve the complete job object for the referenced job. - This is only available to users with manage_recruiting - permissions within the team that the job is posted in. - - *Parameters:* - :job_reference: Job reference - - """ - url = 'jobs/{0}'.format(job_reference) - result = self.get(url) - return result.get('job', result) - - def post_job(self, buyer_team_reference, title, job_type, description, - visibility, budget=None, duration=None, start_date=None, - skills=None, category2=None, subcategory2=None): - """ - Post a job. - - *Parameters:* - :buyer_team_reference: Reference ID of the buyer team that is - posting the job, e.g. 34567 - - :title: Title of the Job - - :job_type: Type of posted job, e.g. "hourly" - Possible values are: - * 'hourly' - * 'fixed-price' - - :description: The job's description - - :visibility: The job's visibility, e.g. 'private'. - Possible values are: - - 'public' jobs are available to all - users who search jobs - - 'private' job is visible to - employer only - - 'upwork' jobs appear in search - results only for Upwork users - who are logged into the service - - 'invite-only' jobs do not appear - in search and are used for jobs - where the buyer wants to control - the potential applicants - - :budget: (conditionally optional) The budget of the - Job, e.g. 100. Is used for 'fixed-price' - jobs only. - - :duration: (conditionally optional) The duration of the - job in hours, e.g. 90. Used for - 'hourly-jobs' only. - - :start_date: (optional) The start date of the Job, - e.g. 06-15-2011. If start_date is not - included the job will default to - starting immediately. - - :skills: (optional) Skills required for the job. - Must be a list or tuple even of one item, - e.g. ``['python']`` - - :category2: (conditionally optional) The category (V2) of job, e.g. - 'Development' - (where to get? - see Metadata API, List Categories (V2)) - - - :subcategory2: (conditionally optional) The subcategory (V2) of job, e.g. - 'Web & Mobile Programming' - (where to get? - see Metadata API, List Categories (V2)) - - """ - url = 'jobs' - data = {} - - data['buyer_team__reference'] = buyer_team_reference - data['title'] = title - - assert_parameter('job_type', job_type, self.JOB_TYPES) - data['job_type'] = job_type - - data['description'] = description - - assert_parameter('visibility', visibility, self.JOB_VISIBILITY_OPTIONS) - data['visibility'] = visibility - - if category2 is None or subcategory2 is None: - raise ApiValueError('sub/category2 parameters must be specified') - - if category2: - data['category2'] = category2 - - if subcategory2: - data['subcategory2'] = subcategory2 - - if budget is None and duration is None: - raise ApiValueError('Either one of the ``budget``or ``duration`` ' - 'parameters must be specified') - - if budget: - data['budget'] = budget - if duration: - data['duration'] = duration - if start_date: - data['start_date'] = start_date - if skills: - data['skills'] = ';'.join(skills) - - result = self.post(url, data) - return result - - def update_job(self, job_id, buyer_team_reference, title, description, - visibility, budget=None, duration=None, start_date=None, - status=None, category2=None, subcategory2=None): - """ - Update a job. - - *Parameters:* - :job_id: Job reference ID - - :buyer_team_reference: Reference ID of the buyer team that is - posting the job, e.g. 34567 - - :title: Title of the Job - - :description: The job's description - - :visibility: The job's visibility, e.g. 'private'. - Possible values are: - - 'public' jobs are available to all - users who search jobs - - 'private' job is visible to - employer only - - 'upwork' jobs appear in search - results only for Upwork users - who are logged into the service - - 'invite-only' jobs do not appear - in search and are used for jobs - where the buyer wants to control - the potential applicants - - :budget: (conditionally optional) The budget of the - Job, e.g. 100. Is used for 'fixed-price' - jobs only. - - :duration: (conditionally optional) The duration of the - job in hours, e.g. 90. Used for - 'hourly-jobs' only. - - :start_date: (optional) The start date of the Job, - e.g. 06-15-2011. If start_date is not - included the job will default to - starting immediately. - - :status: (required) The status of the job, - e.g. 'filled'. - Possible values are: - - 'open' - - 'filled' - - 'cancelled' - - :category2: (conditionally optional) The category (V2) of job, e.g. - 'Development' - (where to get? - see Metadata API, List Categories (V2)) - - - :subcategory2: (conditionally optional) The subcategory (V2) of job, e.g. - 'Web & Mobile Programming' - (where to get? - see Metadata API, List Categories (V2)) - - """ - url = 'jobs/{0}'.format(job_id) - data = {} - - data['buyer_team__reference'] = buyer_team_reference - data['title'] = title - data['description'] = description - - assert_parameter('visibility', visibility, self.JOB_VISIBILITY_OPTIONS) - data['visibility'] = visibility - - data['category2'] = category2 - data['subcategory2'] = subcategory2 - - if budget is None and duration is None: - raise ApiValueError('Either one of the ``budget``or ``duration`` ' - 'parameters must be specified') - - if budget: - data['budget'] = budget - if duration: - data['duration'] = duration - if start_date: - data['start_date'] = start_date - - if status: - assert_parameter('status', status, self.JOB_STATUSES) - data['status'] = status - else: - raise ApiValueError('Missing required parameter "status"') - - return self.put(url, data) - - def delete_job(self, job_id, reason_code): - """ - Delete a job. - - *Parameters:* - :job_id: Job reference ID - - :reason_code: The reason code to cancel the job, e.g. ``41``. - Possible values are: - * ``67`` - Accidental opening creation - * ``51`` - All positions filled - * ``49`` - Filled by alternate source - * ``41`` - Project was cancelled - * ``34`` - No developer for requested skills - - """ - url = 'jobs/{0}'.format(job_id) - return self.delete(url, {'reason_code': reason_code}) - - """offer api""" - - def get_offers(self, buyer_team_reference, include_sub_teams=None, - provider_ref=None, profile_key=None, job_ref=None, - agency_ref=None, status=None, - created_time_from=None, created_time_to=None, - page_offset=0, page_size=20, order_by=None): - """ - Retrieve a list of all the offers on a specific job or within \ - a specific team. - - *Parameters:* - :buyer_team_reference: The buyer's team reference ID - - :include_sub_teams: (optional) <1|0> Whether to include sub teams - - :provider_ref: (optional) The provider's reference ID - - :profile_key: (optional) Unique profile key, used if - ``provider_reference`` is absent - - :job_ref: (optional) The Job's reference ID - - :agency_ref: (optional) The Agency's reference ID - - :status: (optional) Engagement status, - e.g., status=active;closed - - :created_time_from: (optional) timestamp \ - e.g.'2008-09-09 00:00:01' - - :created_time_to: (optional) timestamp \ - e.g.'2008-09-09 00:00:01' - - :page_offset: (optional) Number of entries to skip - - :page_size: (optional: default 20) Page size in number - of entries - - :order_by: (optional) Sorting - - """ - url = 'offers' - data = {} - data['buyer_team__reference'] = buyer_team_reference - - if include_sub_teams: - data['include_sub_teams'] = include_sub_teams - - if provider_ref: - data['provider__reference'] = provider_ref - - if profile_key: - data['profile_key'] = profile_key - - if job_ref: - data['job__reference'] = job_ref - - if agency_ref: - data['agency_team__reference'] = agency_ref - - if status: - data['status'] = status - - if created_time_from: - data['created_time_from'] = created_time_from - - if created_time_to: - data['created_time_to'] = created_time_to - - data['page'] = '{0};{1}'.format(page_offset, page_size) - - if order_by is not None: - data['order_by'] = order_by - - result = self.get(url, data) - return result.get('offers', result) - - def get_offer(self, offer_reference): - """ - Retrieve the referenced offer. - - *Parameters:* - :offer_reference: Offer reference ID - - """ - url = 'offers/{0}'.format(offer_reference) - result = self.get(url) - return result.get('offer', result) - - def post_offer(self, job_reference, provider_team_reference=None, - provider_reference=None, profile_key=None, - message_from_buyer=None, engagement_title=None, - attached_doc=None, fixed_charge_amount_agreed=None, - fixed_pay_amount_agreed=None, - fixed_price_upfront_payment=None, hourly_pay_rate=None, - weekly_salary_charge_amount=None, - weekly_salary_pay_amount=None, weekly_stipend_hours=None, - weekly_hours_limit=None, start_date=None, keep_open=None): - """Make an offer to the provider. - - *Parameters:* - :job_reference: The Job's reference ID - - :provider_team_reference: (optional) The reference ID - of the provider team. If specified, - the check is performed whether user - you're making offer to belongs - to the team - - :provider_reference: (conditionally optional) - The provider's reference. Has - the override priority over - ``profile_key`` if both specified. - - :profile_key: (conditionally optional) - Unique profile key, - used if ``provider_reference`` - is absent - - :message_from_buyer: (optional) Text message - - :engagement_title: (optional) The engagement title - - :attached_doc: (optional) Attachment - - :fixed_charge_amount_agreed: (optional) The amount of agreed - charge, required by fixed-price job - - :fixed_pay_amount_agreed: (optional) The amount of agreed pay - - :fixed_price_upfront_payment: (optional) The amount of upfront - payment - - :hourly_pay_rate: (optional) Hourly pay rate - - :weekly_salary_charge_amount: (optional) Salary charge amount - per week - - :weekly_salary_pay_amount: (optional) Salary pay amount per - week, required by fixed-price job - - :weekly_stipend_hours: (optional) Stipend hours per week - - :weekly_hours_limit: (optional) Limit of hours per week - - :start_date: (optional) The offer start date - - :keep_open: (optional, default: 'no') - Leave the job opened. - Possible values are: 'yes', 'no' - - When just job and provider reference params are provided in the request - then an invitation for an interview should be send to the according - provider. Provider can either choose to accept the invitation and start - the communication with the user or decline it, in which case - communication between client and provider stops. - - When additionally engagement title and charge amount params are - provided in the request, then an actual offer is created for the - provider. In this case the provider can either accept the offer - and start working or decline the offer. - - """ - url = 'offers' - - data = {} - data['job__reference'] = job_reference - - if provider_team_reference: - data['provider_team__reference'] = provider_team_reference - - if profile_key is None and provider_reference is None: - raise ApiValueError('Either one of the parameters ``profile_key`` ' - 'or ``provider_reference`` should be provided') - - if provider_reference: - data['provider__reference'] = provider_reference - if profile_key: - data['profile_key'] = profile_key - - if message_from_buyer: - data['message_from_buyer'] = message_from_buyer - - if engagement_title: - data['engagement_title'] = engagement_title - - if attached_doc: - data['attached_doc'] = attached_doc - - if fixed_charge_amount_agreed: - data['fixed_charge_amount_agreed'] = fixed_charge_amount_agreed - - if fixed_pay_amount_agreed: - data['fixed_pay_amount_agreed'] = fixed_pay_amount_agreed - - if fixed_price_upfront_payment: - data['fixed_price_upfront_payment'] = fixed_price_upfront_payment - - if hourly_pay_rate: - data['hourly_pay_rate'] = hourly_pay_rate - - if weekly_salary_charge_amount: - data['weekly_salary_charge_amount'] = weekly_salary_charge_amount - - if weekly_salary_pay_amount: - data['weekly_salary_pay_amount'] = weekly_salary_pay_amount - - if weekly_stipend_hours: - data['weekly_stipend_hours'] = weekly_stipend_hours - - if weekly_hours_limit: - data['weekly_hours_limit'] = weekly_hours_limit - - if start_date: - data['start_date'] = start_date - - if keep_open: - assert_parameter('keep_open', keep_open, - self.JOB_KEEP_OPEN_OPTIONS) - data['keep_open'] = keep_open - - return self.post(url, data) - - """engagement api""" - - def get_engagements(self, buyer_team_reference=None, - include_sub_teams=None, provider_reference=None, - profile_key=None, job_reference=None, - agency_team_reference=None, status=None, - created_time_from=None, created_time_to=None, - page_offset=0, page_size=20, order_by=None): - """ - Retrieve engagements. - - *Parameters:* - :buyer_team_reference: (optional) The team reference ID - - :include_sub_teams: (optional) <0|1> - whether to include info - about sub-teams - - :provider_reference: (optional) - The provider's reference ID. - Has the override priority over the - ``profile_key`` if both specified. - - :profile_key: (optional) Unique profile key, - used if ``provider_reference`` is absent - - :job_reference: (optional) The Job's reference ID - - :agency_team_reference: (optional) The Agency's reference ID - - :status: (optional) Engagement status, e.g. - ``status=active;closed`` - - :created_time_from: (optional) timestamp \ - e.g.'2008-09-09 00:00:01' - - :created_time_to: (optional) timestamp \ - e.g.'2008-09-09 00:00:01' - - :page_offset: (optional) Number of entries to skip - - :page_size: (optional: default 20) Page size - in number of entries - - :order_by: (optional) Sorting, in format - $field_name1;$field_name2;..$field_nameN;AD...A, - where A means "Ascending", D means "Descending", - available fields are: - * 'reference', - * 'created_time', - * 'offer__reference', - * 'job__reference', - * 'buyer_team__reference', - * 'provider__reference', - * 'status', - * 'engagement_start_date', - * 'engagement_end_date' - """ - url = 'engagements' - - data = {} - if buyer_team_reference: - data['buyer_team__reference'] = buyer_team_reference - - if include_sub_teams: - data['include_sub_teams'] = include_sub_teams - - if profile_key: - data['profile_key'] = profile_key - - if provider_reference: - data['provider_reference'] = provider_reference - - if provider_reference: - data['provider__reference'] = provider_reference - if profile_key: - data['profile_key'] = profile_key - - if job_reference: - data['job__reference'] = job_reference - - if agency_team_reference: - data['agency_team_reference'] = agency_team_reference - - if status: - data['status'] = status - - if created_time_from: - data['created_time_from'] = created_time_from - - if created_time_to: - data['created_time_to'] = created_time_to - - data['page'] = '{0};{1}'.format(page_offset, page_size) - - if order_by is not None: - data['order_by'] = order_by - - result = self.get(url, data) - return result.get('engagements', result) - - def get_engagement(self, engagement_reference): - """ - Retrieve referenced engagement object. - - *Parameters:* - :engagement_reference: Engagement reference ID - - """ - url = 'engagements/{0}'.format(engagement_reference) - result = self.get(url) - return result.get('engagement', result) - - """contracts api""" - - def suspend_contract(self, contract_reference, message): - """ - Suspend the Contract. - - *Parameters:* - :contract_reference: The Contract's reference ID - - :message: A message/reason for contractor - - """ - url = 'contracts/{0}/suspend'.format(contract_reference) - data = {} - - data['message'] = message - - return self.put(url, data) - - def restart_contract(self, contract_reference, message): - """ - Restart the Contract. - - *Parameters:* - :contract_reference: The Contract's reference ID - - :message: A message/reason for contractor - - """ - url = 'contracts/{0}/restart'.format(contract_reference) - data = {} - - data['message'] = message - - return self.put(url, data) - - def end_contract(self, contract_reference, reason, would_hire_again, - fb_scores=None, fb_comment=None): - """ - Close the referenced contract. - - *Parameters:* - :contract_reference: The Contract's reference ID - - :reason: The reason key, e.g. - 'API_REAS_HIRED_DIFFERENT'. - Possible values are: - - 'API_REAS_MISREPRESENTED_SKILLS' - - "Contractor misrepresented his/her skills" - - 'API_REAS_CONTRACTOR_NOT_RESPONSIVE' - - "Contractor not responsive" - - 'API_REAS_HIRED_DIFFERENT' - - "Hired a different contractor" - - 'API_REAS_JOB_COMPLETED_SUCCESSFULLY' - - "Job was completed successfully" - - 'API_REAS_WORK_NOT_NEEDED' - - "No longer need this work completed" - - 'API_REAS_UNPROFESSIONAL_CONDUCT' - - "Unprofessional conduct" - - :would_hire_again: Whether you would hire a contractor again. - Required if total charge on the contract - is $0. - Possible values are: ['yes', 'no'] - - :fb_scores: (optional) Estimate, a dictionary of - scores, where id is reference to - score description (see example below). - The feedback scores are optional, but if - present they must be complete: all scores. - Below are the possible score reference id - values. - - Feedback on contractor: - - * ``3`` - "Skills / competency and skills - for the job, understanding of - specifications/instructions" - * ``4`` - "Quality / quality of work - deliveries" - * ``5`` - "Availability / online presence - on a consistent schedule" - * ``6`` - "Deadlines / ability to complete - tasks on time" - * ``7`` - "Communication / communication - skills, frequent - progress updates, - responsiveness" - * ``8`` - "Cooperation / cooperation and - flexibility, suggestions for - improvement" - - Feedback on employer: - * ``9`` - "Skills / competency and skills - for the job, understanding of task - complexities" - * ``10`` - "Quality / quality of - specifications/instructions" - * ``11`` - "Availability / online presence - on a consistent schedule" - * ``12`` - "Deadlines / understanding of - complexities and trade-offs" - * ``13`` - "Communication / communication - skills and responsiveness, feedback - and guidance" - * ``14`` - "Cooperation / cooperation and - flexibility, open to suggestions - for improvement" - Example: - {'fb_scores[3]': 5, 'fb_scores[4]': 4, ... , - 'fb_scores[8]': 5} - - :fb_comment: (optional) Feedback comment, some string - message. It is optional but if present, then - the ``fb_scores`` parameter is also required. - - """ - url = 'contracts/{0}'.format(contract_reference) - data = {} - - assert_parameter('reason', reason, self.CONTRACT_REASON_OPTIONS) - data['reason'] = reason - - assert_parameter('would_hire_again', would_hire_again, - self.CONTRACT_WOULD_HIRE_AGAIN_OPTONS) - data['would_hire_again'] = would_hire_again - - if fb_scores: - for key, value in fb_scores.items(): - data[key] = value - - if fb_comment: - data['fb_comment'] = fb_comment - - result = self.delete(url, data) - return result - - -class HR_V3(Namespace): - """ - HR API version 3. - """ - api_url = 'hr/' - version = 3 - - def create_milestone(self, contract_reference, milestone_description, deposit_amount, due_date=None): - """ - Create a milestone. - - *Parameters:* - :contract_reference: Contract reference. Contracts info are available in the Engagements API. - :milestone_description: Name of the milestone. - :deposit_amount: Amount to deposit for this milestone. - :due_date: (optional) Expected date of finalization. Format mm-dd-yyyy. - - """ - data = {} - data['contract_reference'] = contract_reference - data['milestone_description'] = milestone_description - data['deposit_amount'] = deposit_amount - - if due_date: - data['due_date'] = due_date - - url = 'fp/milestones' - return self.post(url, data) - - def edit_milestone(self, milestone_id, milestone_description=None, deposit_amount=None, due_date=None, message=None): - """ - Edit an existing milestone. - - *Parameters:* - :milestone_id: Milestone reference. - :milestone_description: (optional) Name of the milestone. - :deposit_amount: (optional) Amount to deposit for this milestone. - :due_date: (optional) Expected date of finalization. Format mm-dd-yyyy. - :message: (optional) Message from the client to the freelancer. - - """ - data = {} - data['milestone_id'] = milestone_id - - if milestone_description: - data['milestone_description'] = milestone_description - - if deposit_amount: - data['deposit_amount'] = deposit_amount - - if due_date: - data['due_date'] = due_date - - if message: - data['message'] = message - - url = 'fp/milestones/{0}'.format(milestone_id) - return self.put(url, data) - - def activate_milestone(self, milestone_id, message=None): - """ - Activates a milestone that has not been funded. - - *Parameters:* - :milestone_id: Milestone reference. - :message: (optional) Message from the client to the freelancer. - - """ - data = {} - data['milestone_id'] = milestone_id - - if message: - data['message'] = message - - url = 'fp/milestones/{0}/activate'.format(milestone_id) - return self.put(url, data) - - def approve_milestone(self, milestone_id, amount=None, bonus=None, pay_comments=None, underpayment_reason=None, note2contractor=None): - """ - Approve the milestone. - - *Parameters:* - :milestone_id: Milestone reference. - :amount: (optional) Amount of money to be paid. If none provided, the full deposit_amount is paid. - :bonus: (optional) Amount of money paid as bonus. - :pay_comments: (optional) Comments on the payment. - :underpayment_reason: (optional) Reason for a smaller payment than the one agreed. - Valid values: `329`, `330`, `331`, `332` - :note2contractor: (optional) Notes from the client to the freelancer. - - """ - data = {} - data['milestone_id'] = milestone_id - - if amount: - data['amount'] = amount - - if bonus: - data['bonus'] = bonus - - if pay_comments: - data['pay_comments'] = pay_comments - - if underpayment_reason: - data['underpayment_reason'] = underpayment_reason - - if note2contractor: - data['note2contractor'] = note2contractor - - url = 'fp/milestones/{0}/approve'.format(milestone_id) - return self.put(url, data) - - def delete_milestone(self, milestone_id): - """ - Delete the milestone. - - *Parameters:* - :milestone_id: Milestone reference. - - """ - data = {} - - url = 'fp/milestones/{0}'.format(milestone_id) - return self.delete(url, data) - - def request_submission_approval(self, milestone_id, note2client, amount): - """ - Freelancer submits work for the client to approve. - - *Parameters:* - :milestone_id: Milestone reference. - :note2client: Notes from freelancer to client about work that was done. - :amount: Amount requested by the freelancer. - - """ - data = {} - data['milestone_id'] = milestone_id - data['note2client'] = note2client - data['amount'] = amount - - url = 'fp/submissions' - return self.post(url, data) - - def approve_submission(self, submission_id, amount, bonus=None, pay_comments=None, underpayment_reason=None, note2contractor=None): - """ - Approve the submission. - - *Parameters:* - :submission_id: Submission reference. - :amount: Amount of money to be paid. If none provided, the full deposit_amount is paid. - :bonus: (optional) Amount of money paid as bonus. - :pay_comments: (optional) Comments on the payment. - :underpayment_reason: (optional) Reason for a smaller payment than the one agreed. - Valid values: `329`, `330`, `331`, `332` - :note2contractor: (optional) Notes from the client to the freelancer. - - """ - data = {} - data['submission_id'] = submission_id - data['amount'] = amount - - if bonus: - data['bonus'] = bonus - - if pay_comments: - data['pay_comments'] = pay_comments - - if underpayment_reason: - data['underpayment_reason'] = underpayment_reason - - if note2contractor: - data['note2contractor'] = note2contractor - - url = 'fp/submissions/{0}/approve'.format(submission_id) - return self.put(url, data) - - def reject_submission(self, submission_id, note2contractor): - """ - Reject the submission. - - *Parameters:* - :submission_id: Submission reference. - :note2contractor: Notes from the client to the freelancer. - - """ - data = {} - data['submission_id'] = submission_id - data['note2contractor'] = note2contractor - - url = 'fp/submissions/{0}/reject'.format(submission_id) - return self.put(url, data) - - def get_milestone_submissions(self, milestone_id): - """ - Get all submissions for specific milestone. - - *Parameters:* - :milestone_id: Milestone reference ID. - - """ - data = {} - - url = 'fp/milestones/{0}/submissions'.format(milestone_id) - return self.get(url, data) - - def get_active_milestone(self, contract_reference): - """ - Get active milestone for specific contract. - - *Parameters:* - :contract_reference: Contract reference. Contracts info are available in the Engagements API. - - """ - data = {} - - url = 'fp/milestones/statuses/active/contracts/{0}'.format(contract_reference) - return self.get(url, data) - - -class HR_V4(Namespace): - """ - HR API version 4. - """ - api_url = 'hr/' - version = 4 - - def list_client_applications(self, buyer_team__reference, job_key, - status=None, profile_key=None, - agency_team__reference=None, - order_by=None, page_offset=None, - page_size=None): - """ - List job applications as a client. - - *Parameters:* - :buyer_team__reference: The reference ID of the client's team. - It allows getting applications for a - specific team. Example: ``34567``. - Use 'List Teams' API call to get it. - - :job_key: The job key. It allows getting applications - for a specific job. Example: ``~01d54a7xxxxx125731``. - - :status: (optional) The current status of the job application. - Valid values: ``shortlisted``, ``messaged``, - ``hired``, ``offered``, ``declined``, ``hidden``. - - :profile_key: (optional) Filters by a specific freelancer's profile key. - - :agency_team__reference: (optional) The reference ID of the agency. - - :order_by: (optional) Sorts results in format ``$field_name1;$field_name2;..$field_nameN;AD...A``. - Here ``A`` resebles ascending order, ``D`` - descending order. Example: ``order_by=created_time;D``. - - :page_offset: (optional) Number of entries to skip - - :page_size: (optional: default 20) Page size - in number of entries - - """ - data = {} - - data['buyer_team__reference'] = buyer_team__reference - data['job_key'] = job_key - - if status: - data['status'] = status - - if profile_key: - data['profile_key'] = profile_key - - if agency_team__reference: - data['agency_team__reference'] = agency_team__reference - - if order_by: - data['order_by'] = order_by - - data['page'] = '{0};{1}'.format(page_offset, page_size) - - url = 'clients/applications' - return self.get(url, data) - - def get_client_application(self, application_id, buyer_team__reference): - """ - Get specific job application as a client. - - *Parameters:* - :application_id: Job application reference ID. - - :buyer_team__reference: The reference ID of the client's team. - It allows getting applications for a - specific team. Example: ``34567``. - Use 'List Teams' API call to get it. - - """ - data = {} - - data['buyer_team__reference'] = buyer_team__reference - - url = 'clients/applications/{0}'.format(application_id) - return self.get(url, data) - - def list_freelancer_applications(self, status=None, page_offset=None, page_size=None): - """ - List job applications as a freelancer. - - *Parameters:* - :status: (optional) The current status of the job application. - Valid values: ``interviews``, ``invites``, ``active``. - - :page_offset: (optional) Number of entries to skip - - :page_size: (optional: default 20) Page size - in number of entries - - """ - data = {} - - if status: - data['status'] = status - - if page_offset and page_size: - data['page'] = '{0};{1}'.format(page_offset, page_size) - - url = 'contractors/applications' - return self.get(url, data) - - def get_freelancer_application(self, application_id): - """ - Get specific job application as a freelancer. - - *Parameters:* - :application_id: Job application reference ID. - - """ - data = {} - - url = 'contractors/applications/{0}'.format(application_id) - return self.get(url, data) diff --git a/upwork/routers/job.py b/upwork/routers/job.py deleted file mode 100644 index 07c81f8..0000000 --- a/upwork/routers/job.py +++ /dev/null @@ -1,47 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork - -from upwork.namespaces import Namespace - - -class Job(Namespace): - api_url = 'profiles/' - version = 1 - - def get_job_profile(self, job_key): - """Returns detailed profile information about job(s). - - Documented at - https://developers.upwork.com/?lang=python#jobs_get-job-profile - - *Parameters:* - :job_key: The job key or a list of keys, separated by ";", - number of keys per request is limited by 20. - You can access job profile by job reference number, - in that case you can't specify a list of jobs, - only one profile per request is available. - - """ - max_keys = 20 - url = 'jobs/{0}' - # Check job key(s) - if not job_key.__class__ in [str, int, list, tuple]: - raise ValueError( - 'Invalid job key. Job recno, key or list of keys expected, ' + - '{0} given'.format(job_key.__class__)) - elif job_key.__class__ in [list, tuple]: - if len(job_key) > max_keys: - raise ValueError( - 'Number of keys per request is limited by {0}'.format( - max_keys)) - elif tuple(filter(lambda x: not str(x).startswith('~~'), job_key)): - raise ValueError( - 'List should contain only job keys not recno.') - else: - url = url.format(';'.join(job_key)) - else: - url = url.format(job_key) - result = self.get(url) - profiles = result.get('profiles', result) - return profiles.get('profile', result) diff --git a/upwork/routers/messages.py b/upwork/routers/messages.py deleted file mode 100644 index 755194a..0000000 --- a/upwork/routers/messages.py +++ /dev/null @@ -1,168 +0,0 @@ -# Python bindings to Upwork API -# python-upwork -# (C) 2010-2016 Upwork - -import urllib - -from upwork.namespaces import Namespace - - -class Messages(Namespace): - api_url = 'messages/' - version = 3 - - def get_rooms(self, company, params = {}): - """ - Retrieve rooms information - - *Parameters:* - :company: Company ID - - :params: List of parameters - - """ - url = '{0}/rooms'.format(company) - result = self.get(url, data=params) - return result.get(url, result) - - def get_room_details(self, company, room_id, params = {}): - """ - Get a specific room information - - *Parameters:* - :company: Company ID - - :room_id Room ID - - :params: List of parameters - - """ - url = '{0}/rooms/{1}'.format(company, room_id) - result = self.get(url, data=params) - return result.get(url, result) - - def get_room_messages(self, company, room_id, params = {}): - """ - Get messages from a specific room - - *Parameters:* - :company: Company ID - - :room_id Room ID - - :params: List of parameters - - """ - url = '{0}/rooms/{1}/stories'.format(company, room_id) - result = self.get(url, data=params) - return result.get(url, result) - - def get_room_by_offer(self, company, offer_id, params = {}): - """ - Get a specific room by offer ID - - *Parameters:* - :company: Company ID - - :offer_id: Offer ID - - :params: List of parameters - - """ - url = '{0}/rooms/offers/{1}'.format(company, offer_id) - result = self.get(url, data=params) - return result.get(url, result) - - def get_room_by_application(self, company, application_id, params = {}): - """ - Get a specific room by application ID - - *Parameters:* - :company: Company ID - - :application_id: Application ID - - :params: List of parameters - - """ - url = '{0}/rooms/applications/{1}'.format(company, application_id) - result = self.get(url, data=params) - return result.get(url, result) - - def get_room_by_contract(self, company, contract_id, params = {}): - """ - Get a specific room by contract ID - - *Parameters:* - :company: Company ID - - :contract_id: Contract ID - - :params: List of parameters - - """ - url = '{0}/rooms/contracts/{1}'.format(company, contract_id) - result = self.get(url, data=params) - return result.get(url, result) - - def create_room(self, company, params = {}): - """ - Create a new room - - *Parameters:* - :company: Company ID - - :params: List of parameters - - """ - url = '{0}/rooms'.format(company) - result = self.post(url, data=params) - return result.get(url, result) - - def send_message_to_room(self, company, room_id, params = {}): - """ - Send a message to a room - - *Parameters:* - :company: Company ID - - :room_id: Room ID - - :params: List of parameters - - """ - url = '{0}/rooms/{1}/stories'.format(company, room_id) - result = self.post(url, data=params) - return result.get(url, result) - - def update_room_settings(self, company, room_id, username, params = {}): - """ - Update a room settings - - *Parameters:* - :company: Company ID - - :room_id: Room ID - - :params: List of parameters - - """ - url = '{0}/rooms/{1}/users/{2}'.format(company, room_id, username) - result = self.put(url, data=params) - return result.get(url, result) - - def update_room_metadata(self, company, room_id, params = {}): - """ - Update the metadata of a room - - *Parameters:* - :company: Company ID - - :room_id: Room ID - - :params: List of parameters - - """ - url = '{0}/rooms/{1}'.format(company, room_id) - result = self.put(url, data=params) - return result.get(url, result) diff --git a/upwork/routers/offers.py b/upwork/routers/offers.py deleted file mode 100644 index 62489ca..0000000 --- a/upwork/routers/offers.py +++ /dev/null @@ -1,275 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork - -from upwork.namespaces import Namespace -from upwork.utils import assert_parameter, ApiValueError - - -class Offers(Namespace): - """ - Offers API version 1 - """ - api_url = 'offers/' - version = 1 - - def list_client_offers(self, company_id=None, team__reference=None, - job__reference=None, status=None, - page_offset=None, page_size=None): - """ - List client offers. - - *Parameters:* - :company_id: (optional) The client's company reference ID. - Example: ``34567``. Get it using 'List companies' - API call. If ``company_id`` is not specified, - the API will infer it from job's data relying on - the provided ``job__reference`` parameter. Either - ``company_id`` or ``job__reference`` parameter must be specified. - - :team__reference: (optional) The client's team reference ID. Example: ``34567``. Get it using - 'List teams' API call. Please note, that if ``team__reference`` - is not specified, the API will infer it from job's data relying on the provided. - - :job__reference: (optional) The job reference ID. Get it using 'List jobs' API call. - - :status: (optional) The current status of the Offer. By default only offers in status `new` are returned. - Valid values: ``accepted``, ``new``, ``declined``, - ``expired``, ``withdrawn``, ``cancelled``, ``changed``. - - :page_offset: (optional) Number of entries to skip - - :page_size: (optional: default 20) Page size in number - of entries - - """ - data = {} - - if company_id: - data['company_id'] = company_id - - if team__reference: - data['team__reference'] = team__reference - - if job__reference: - data['job__reference'] = job__reference - - if status: - data['status'] = status - - if page_offset: - data['page'] = '{0};{1}'.format(page_offset, page_size) - - url = 'clients/offers' - return self.get(url, data) - - def send_client_offer(self, title, job_type, charge_rate, - message_to_contractor, team__reference=None, - client_team_ref=None, contractor_username=None, - contractor_reference=None, contractor_key=None, - contractor_org=None, context=None, - charge_upfront_percent=None, weekly_limit=None, - weekly_stipend=None, expires_on=None, - close_on_accept=None, related_jobcategory=None, - milestones=None, related_jobcategory2=None): - """ - Send client offer to the freelancer. - - *Parameters:* - :title: The title of the offer/contract. - - :job_type: The type of the job. Valid values: ``hourly``, ``fixed-price``. - - :charge_rate: The budget amount for fixed-price jobs or the hourly charge rate for hourly jobs. - - :message_to_contractor: Instructions and other job details for the freelancer. - - :team__reference: The client's team reference ID. Example: ``34567``. Use 'List teams' API call to get it. - - :client_team_ref: The client's team reference. Example: ``mytestcompany:myteam``. Use 'List teams' API call to get it. - - :contractor_username: The freelancer's username. Example: ``contractoruid``. - It can be ignored if ``contractor_reference`` or ``contractor_key`` parameter is set. - - :contractor_reference: The freelancer's reference ID. It will be ignored if ``contractor_username`` or ``contractor_key`` - is specified. Example: ``1234``. You can use 'Search freelancers' API call to get the freelancer's reference ID. - - :contractor_key: The unique profile key, used if ``contractor_username`` is absent. Example: ``~~677961dcd7f65c01``. - - :contractor_org: The freelancer's team reference, required for sending offers to agency freelancers. - - :context: Additional data about the offer. Valid array keys are: ``previous_offer_ref``, - ``job_posting_ref``, ``job_application_ref``, ``contract_ref``. - Example: ``context[job_posting_ref] = {{ opening_id }} & context[job_application_ref] = {{ application_id }}`` - where ``job_posting_ref`` is a job key, for example ``~01c8e0xxxxxxxx05255``. - - :charge_upfront_percent: (deprecated) This parameter remains for compatibility reasons and will be removed - in next releases those come after February 6th 2015. - The percentage of the budget amount that the freelancer is paid on acceptance of the offer - (for fixed price jobs only). - - :weekly_limit: The maximum number of hours per week the freelancer can bill for. - - :weekly_stipend: An additional payment to be issued to the freelancer each week. - - :expires_on: (deprecated) This parameter remains for compatibility reasons and will be removed - in next releases those come after February 6th 2015. - Time when the offer expires. This should be a UNIX UTC timestamp. For example: ``1400785324``. - - :close_on_accept: If the value is ``1``, it automatically closes the related job post if this offer is accepted. - The default value is ``1``. Valid values: ``0``, ``1``. - - :related_jobcategory: Related job category. For example: ``9``. - - :milestones: (required after February 6th 2015) Array of milestones for fixed-priced jobs in the following format: - `milestones[0][$key]`, ..., `milestones[N][$key]`, where key is one of the following - - `milestone_description` (string), `deposit_amount` (float), `due_date` (string in format mm-dd-yyyy) - - :related_jobcategory2: Related job category (V2). For example: ``531770282584862733``. - - """ - data = {} - - data['title'] = title - data['job_type'] = job_type - data['charge_rate'] = charge_rate - data['message_to_contractor'] = message_to_contractor - - if team__reference: - data['team__reference'] = team__reference - - if client_team_ref: - data['client_team_ref'] = client_team_ref - - if contractor_username: - data['contractor_username'] = contractor_username - - if contractor_reference: - data['contractor_reference'] = contractor_reference - - if contractor_key: - data['contractor_key'] = contractor_key - - if charge_upfront_percent: - data['charge_upfront_percent'] = charge_upfront_percent - - if context: - for k, v in context.iteritems(): - key = 'context[{0}]'.format(k) - data[key] = v - - if weekly_limit: - data['weekly_limit'] = weekly_limit - - if weekly_stipend: - data['weekly_stipend'] = weekly_stipend - - if expires_on: - data['expires_on'] = expires_on - - if close_on_accept: - data['close_on_accept'] = close_on_accept - - if related_jobcategory: - data['related_jobcategory'] = related_jobcategory - - if related_jobcategory2: - data['related_jobcategory2'] = related_jobcategory2 - - if milestones: - for idx, val in enumerate(milestones): - for k, v in val.iteritems(): - key = 'milestones[{0}][{1}]'.format(idx, k) - data[key] = v - - url = 'clients/offers' - return self.post(url, data) - - def get_client_offer(self, offer_id, company_id=None, job__reference=None): - """ - Get offer as client. - - *Parameters:* - :offer_id: Offer reference ID. - - :company_id: (optional) The client's company reference ID. - Example: ``34567``. Get it using 'List companies' - API call. If ``company_id`` is not specified, - the API will infer it from job's data relying on - the provided ``job__reference`` parameter. Either - ``company_id`` or ``job__reference`` parameter must be specified. - - :job__reference: (optional) The job reference ID. Get it using 'List jobs' API call. - - """ - data = {} - - data['offer_id'] = offer_id - - if company_id: - data['company_id'] = company_id - - if job__reference: - data['job__reference'] = job__reference - - url = 'clients/offers/{0}'.format(offer_id) - return self.get(url, data) - - def list_freelancer_offers(self, status=None, - page_offset=None, page_size=None): - """ - List freelancer's offers. - - *Parameters:* - :status: (optional) The current status of the Offer. By default only offers in status `new` are returned. - Valid values: ``accepted``, ``new``, ``declined``, - ``expired``, ``withdrawn``, ``cancelled``, ``changed``. - - :page_offset: (optional) Number of entries to skip - - :page_size: (optional: default 20) Page size in number - of entries - - """ - data = {} - - if status: - data['status'] = status - - if page_offset: - data['page'] = '{0};{1}'.format(page_offset, page_size) - - url = 'contractors/offers' - return self.get(url, data) - - def get_freelancer_offer(self, offer_id): - """ - Get specific offer as freelancer. - - *Parameters:* - :offer_id: Offer reference ID. - - """ - data = {} - - data['offer_id'] = offer_id - - url = 'contractors/offers/{0}'.format(offer_id) - return self.get(url, data) - - def accept_or_decline(self, offer_id, action_name): - """ - Get specific offer as freelancer. - - *Parameters:* - :offer_id: Offer reference ID. - - :action_name: The name of the action to run. - - """ - data = {} - - data['action_name'] = action_name - - url = 'contractors/actions/{0}'.format(offer_id) - return self.post(url, data) diff --git a/upwork/routers/provider.py b/upwork/routers/provider.py deleted file mode 100644 index 0a3acf3..0000000 --- a/upwork/routers/provider.py +++ /dev/null @@ -1,358 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork - -from upwork.namespaces import Namespace -from upwork.compatibility import quote - - -class Provider(Namespace): - api_url = 'profiles/' - version = 1 - - def get_provider(self, provider_ciphertext): - """ - Retrieve an exhaustive list of attributes associated with the \ - referenced provider. - - *Parameters:* - :provider_ciphertext: The provider's cipher text (key) - - """ - if isinstance(provider_ciphertext, (list, tuple)): - provider_ciphertext = map(str, provider_ciphertext) - provider_ciphertext = quote(';').join(provider_ciphertext[:20]) - - url = 'providers/{0}'.format(provider_ciphertext) - result = self.get(url) - return result.get('profile', result) - - def get_provider_brief(self, provider_ciphertext): - """ - Retrieve an brief list of attributes associated with the \ - referenced provider. - - *Parameters:* - :provider_ciphertext: The provider's cipher text (key) - - """ - if isinstance(provider_ciphertext, (list, tuple)): - provider_ciphertext = map(str, provider_ciphertext) - provider_ciphertext = ';'.join(provider_ciphertext[:20]) - - url = 'providers/{0}/brief'.format(provider_ciphertext) - result = self.get(url) - return result.get('profile', result) - - def get_skills_metadata(self): - """ - Returns list of all skills available for job/contractor profiles. - - """ - url = 'metadata/skills' - result = self.get(url) - return result.get('skills', result) - - def get_regions_metadata(self): - """ - Returns list of all region choices available for \ - job/contractor profiles. - - """ - url = 'metadata/regions' - result = self.get(url) - return result.get('regions', result) - - def get_tests_metadata(self): - """ - Returns list of all available tests at Upwork. - - """ - url = 'metadata/tests' - result = self.get(url) - return result.get('tests', result) - - def get_reasons_metadata(self, reason_type): - """ - Returns a list of reasons by specified type. - - *Parameters:* - :type: Requested type of the reason. Valid values: - ``EmployerEndsNoStartContract``, ``CloseOpening``, - ``RejectCandidate``, ``RejectInterviewInvite``, - ``CancelCandidacy``, ``EndProviderContract``, - ``EndCustomerContract``, ``EndAssignment`` - - """ - url = 'metadata/reasons' - data = {'type': reason_type} - result = self.get(url, data) - return result.get('reasons', result) - - def get_specialties_metadata(self): - """ - Returns list of all specialties available for job/contractor profiles. - - """ - url = 'metadata/specialties' - result = self.get(url) - return result.get('specialties', result) - - -class Provider_V2(Namespace): - api_url = 'profiles/' - version = 2 - - def get_categories_metadata(self): - """ - Returns list of all categories (v2) available for job/contractor profiles. - - """ - url = 'metadata/categories' - result = self.get(url) - return result.get('categories', result) - - def search_providers(self, data=None, page_offset=0, page_size=20): - """Search providers. - - The contractor search API allows to third party applications to search - for any public contractor on Upwork. The search parameters mirror - the options available on the site plus options to configure - the format of your results. - - *Parameters:* - - :data: (optional) A dict of the following parameters - (all parameters are optional): - - :q: The search query. - With v2 API we support a subset of the - ``lucene query syntax``. In particular we support - ``AND``, ``OR``, ``NOT`` and additionally fields - exposed are the ones indicated below, - e.g ``q=title:php AND category:"Web Development"`` - - :title: Search in title of contractor profile - - :skills: Search in skills of contractor profile - - :groups: Search in groups of contractor profile - - :tests: Search in tests of contractor profile - Use - :py:meth:`~upwork.routers.provider.Provider.get_tests_metadata` - to get available tests - - :tests_top_10: Search for contractors that are - in top 10 for test - - :tests_top_30: Search for contractors that are - in top 30 for test - - :category2: Search for category of contractor profile. - Use - :py:meth:`~upwork.routers.provider.Provider.get_categories_metadata` - to get available categories - - :subcategory2: Search for subcategory of contractor profile. - Use - :py:meth:`~upwork.routers.provider.Provider.get_categories_metadata` - to get available categories - - :region: Search for contractor profile in a region - Acceptable values are titles from Metadata Regions API - :py:meth:`~upwork.routers.provider.get_regions`, - e.g. ``Latin America`` - - :feedback: Search for contractor with feedback score: - - single params like ``3`` or ``3,4`` are acceptable - (comma - separated values result to OR queries) - - also ranges like ``[3 TO 4]`` are acceptable - - :rate: Search for contractor profile with rate: - - single params like ``20`` or ``20,30`` - are acceptable - (comma - separated values result to OR queries) - - ranges like ``[20 TO 40]`` are acceptable - - :hours: Search for contractor profile that has worked - this many hours - - single params like ``20`` or ``20,30`` - are acceptable - (comma separated values result to OR queries) - - ranges like ``[20 TO 40]`` are acceptable - - :recent_hours: Search for contractor profile that has worked - this many hours recently: - - single params like ``20`` or ``20,30`` - are acceptable - (comma separated values result to OR queries) - - ranges like ``[20 TO 40]`` are acceptable - - :last_activity: Date of last time contractor worked - - last_activity is searched with ISO 8601 - Date syntax with hours always set at - 00:00:00.000 (i.e. ``2013-01-04T00:00:00.000Z``) - - :english_skill: Assessment of contractor on his/her english skills. - Value can be set to one of ``0 | 1 | 2 | 3 | 4 | 5`` - - :is_upwork_ready: Whether contractor is upwork ready. - Value can be set to ``1`` or ``0`` - - :profile_type: Whether contractor is an AC or an IC. - Possible values: - - ``Independent`` - - ``Agency`` - - :include_entities: Parameter can be set to ``0`` or ``1`` - If ``1``: ``data`` in response will contain - only profile ids array. - - :page_offset: (optional) Start of page (number of results to skip) - - :page_size: (optional: default ``20``) Page size (number of results) - - """ - url = 'search/providers' - search_data = {} - - if data: - search_data.update(data) - - search_data['paging'] = '{0};{1}'.format(page_offset, page_size) - - result = self.get(url, data=search_data) - - return result.get('providers', result) - - def search_jobs(self, data=None, page_offset=0, page_size=20): - """Search jobs. - - The Job search API allows to third party applications to search - for any public job on Upwork. The search parameters mirror the options - available on the site plus options to configure the format - of your results. - - *Parameters:* - - :data: (optional) A dict of the following parameters - (all parameters are optional): - - :q: The search query. - With API v2 we support a subset of the - ``lucene query syntax``. In particular we support - ``AND``, ``OR``, ``NOT`` and additionally fields - exposed are the ones indicated below, - e.g ``q=title:php AND category:web_development`` - - :title: Search in title of job profile - - :skills: Search in skills of job profile - - :groups: Search in groups of job profile - - :tests: Search in tests of job profile - Use - :py:meth:`~upwork.routers.provider.Provider.get_tests_metadata` - to get available tests - - :tests_top_10: Search for jobs that require provider to be - in top 10 for test - - :tests_top_30: Search for jobs that require provider to be - in top 30 for test - - :category2: Search for category of job profile - See full list here: - https://developers.upwork.com/?lang=python#metadata_list-categories - - :subcategory2: Search for subcategory of job profile - See full list here: - https://developers.upwork.com/?lang=python#metadata_list-categories - in the table "Changes" - - :job_type: Type of job. - Acceptable values are: - - ``hourly`` - - ``fixed`` - - :duration: Indicates job duration. - Acceptable values are: - - ``week`` - - ``month`` - - ``quarter`` - - ``semester`` - - ``ongoing`` - - :workload: Indicates workload for the job. - Acceptable values are: - - ``as_needed`` - - ``part_time`` - - ``full_time`` - - :client_feedback: Constrains the search to jobs posted by clients - with rating within a range: - - If the value is ``None``, then jobs from - clients without rating are returned. - - single params like ``1`` or ``2,3`` - are acceptable - (comma separated values result to OR queries) - - ranges like ``[2 TO 4]`` are acceptable - - :client_hires: Constrains the search to jobs from clients - with range within the given number of past hires: - - single params like ``1`` or ``2,3`` - are acceptable - (comma separated values result to OR queries) - - ranges like ``[10 TO 20]`` are acceptable - - :budget: Constrains the search to jobs having the budget - within the range given - - ranges like ``[100 TO 1000]`` are acceptable - - :job_status: The status that the job is currently in - Acceptable values are: - - ``open`` - - ``completed`` - - ``cancelled`` - - :posted_since: Number of days since the job was posted. - - :sort: Field and direction sorting search results. - ``create_time`` descending is used by default. - Allowed sorting fields are: - - ``create_time`` - - ``client_rating`` - - ``client_total_charge`` - - ``client_total_hours`` - - ``score`` - - ``workload`` - - ``duration`` - Example: ``sort=create_time%20desc`` - - :page_offset: (optional) Start of page (number of results to skip) - - :page_size: (optional: default ``20``) Page size (number of results) - - """ - url = 'search/jobs' - search_data = {} - - if data: - search_data.update(data) - - search_data['paging'] = '{0};{1}'.format(page_offset, page_size) - - result = self.get(url, data=search_data) - - return result.get('jobs', result) - - def get_skills_metadata(self): - """ - Returns list of all V2 skills available for job/contractor profiles. - - """ - url = 'metadata/skills' - result = self.get(url) - return result.get('skills', result) diff --git a/upwork/routers/task.py b/upwork/routers/task.py deleted file mode 100644 index be46de9..0000000 --- a/upwork/routers/task.py +++ /dev/null @@ -1,471 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork - -from upwork.compatibility import quote -from upwork.namespaces import Namespace - - -class Task(Namespace): - api_url = 'otask/' - version = 1 - - def get_team_tasks(self, company_id, team_id, - paging_offset=0, paging_count=1000): - """ - Retrieve a list of all activities in the given team. - - This call returns both archived and active activities. - - The user authenticated must have been granted the appropriate - access to the team. - - *Parameters:* - :company_id: Company ID. Use the ``parent_team__id`` value - from ``hr.get_team()`` API call. - - :team_id: Team ID. Use the 'id' value - from ``hr.get_team()`` API call. - - """ - if paging_offset or not paging_count == 1000: - data = {'page': '{0};{1}'.format(paging_offset, - paging_count)} - else: - data = {} - - url = 'tasks/companies/{0}/teams/{1}/tasks'.format(company_id, - team_id) - return self.get(url, data=data) - - def get_company_tasks(self, company_id, - paging_offset=0, paging_count=1000): - """ - Retrieve a list of all activities within a company. - It is equivalent to the ``get_team_tasks`` so that - ``team_id`` is equal to ``company_id`` which is parent - team ID. - - This call returns both archived and active activities. - - The user authenticated must have been granted the appropriate - access to the company. - - *Parameters:* - :company_id: Company ID. Use the ``parent_team__id`` value - from ``hr.get_team()`` API call. - """ - team_id = company_id - return self.get_team_tasks( - company_id, - team_id, - paging_offset=paging_offset, - paging_count=paging_count - ) - - def _encode_task_codes(self, task_codes): - if isinstance(task_codes, (list, tuple)): - return ';'.join(str(c) for c in task_codes) - else: - return str(task_codes) - - def get_team_specific_tasks(self, company_id, team_id, task_codes): - """ - Return a specific activities within a team. - - *Parameters:* - :company_id: Company ID. Use the ``parent_team__id`` value - from ``hr.get_team()`` API call. - - :team_id: Team ID. Use the 'id' value - from ``hr.get_team()`` API call. - - :task_codes: Task codes (must be a list, even of 1 item) - - """ - task_codes = self._encode_task_codes(task_codes) - url = 'tasks/companies/{0}/teams/{1}/tasks/{2}'.format( - company_id, team_id, quote(task_codes)) - result = self.get(url) - try: - return result["tasks"] or [] - except KeyError: - return result - - def get_company_specific_tasks(self, company_id, task_codes): - """ - Return a specific activities within a company. - This is identical to ``get_team_specific_tasks``, - so that ``team_id`` is the same as ``company_id``. - - *Parameters:* - :company_id: Company ID. Use the ``parent_team__id`` value - from ``hr.get_team()`` API call. - - :task_codes: Task codes (must be a list, even of 1 item) - - """ - team_id = company_id - return self.get_team_specific_tasks( - company_id, team_id, task_codes) - - def post_team_task(self, company_id, team_id, code, description, url, - engagements=None, all_in_company=None): - """ - Create an activity within a team. - - The authenticated user needs to have hiring manager privileges - - *Parameters:* - :company_id: Company ID. Use the ``parent_team__id`` value - from ``hr.get_team()`` API call. - - :team_id: Team ID. Use the 'id' value - from ``hr.get_team()`` API call. - - :code: Task code - - :description: Task description - - :url: Task URL - - :engagements: (optional) A list of engagements - that are to be assigned to the created activity. - It can be a single engagement ID, - or an iterable of IDs. - - :all_in_company: (optional) If ``True``, assign the - created activity to all engagements - that are exist in the company at - the moment. - - If both ``engagements`` and ``all_in_company`` are provided, - ``engagements`` list will override the ``all_in_company`` setting. - - """ - post_url = 'tasks/companies/{0}/teams/{1}/tasks'.format( - company_id, team_id) - data = {'code': code, - 'description': description, - 'url': url} - - if engagements: - engagements = self._encode_task_codes(engagements) - data['engagements'] = engagements - - if all_in_company: - data['all_in_company'] = 1 - - result = self.post(post_url, data) - return result - - def post_company_task(self, company_id, code, description, url, - engagements=None, all_in_company=None): - """ - Create an activity within a company. - This call is identical to - ``post_team_task`` so that ``team_id`` is equal - to ``company_id``. - - The authenticated user needs to have hiring manager privileges. - - *Parameters:* - :company_id: Company ID. Use the ``parent_team__id`` value - from ``hr.get_team()`` API call. - - :code: Activity ID - - :description: Activity description - - :url: Activity URL - - :engagements: (optional) A list of engagements - that are to be assigned to the created activity. - It can be a single engagement ID, - or an iterable of IDs. - - :all_in_company: (optional) If ``True``, assign the - created activity to all engagements - that are exist in the company at - the moment. - - If both ``engagements`` and ``all_in_company`` are provided, - ``engagements`` list will override the ``all_in_company`` setting. - - """ - team_id = company_id - return self.post_team_task( - company_id, team_id, code, description, - url, engagements=engagements, all_in_company=all_in_company) - - def put_team_task(self, company_id, team_id, code, description, url, - engagements=None, all_in_company=None): - """ - Update an activity within a team. - - The authenticated user needs to have hiring manager privileges. - - *Parameters:* - :company_id: Company ID. Use the ``parent_team__id`` value - from ``hr.get_team()`` API call. - - :team_id: Team ID. Use the 'id' value - from ``hr.get_team()`` API call. - - - :code: Task code - - :description: Task description - - :url: Task URL - - :engagements: (optional) A list of engagements - that are to be assigned to the created activity. - It can be a single engagement ID, - or an iterable of IDs. - - :all_in_company: (optional) If ``True``, assign the - updated activity to all engagements - that are exist in the company at - the moment. - - If both ``engagements`` and ``all_in_company`` are provided, - ``engagements`` list will override the ``all_in_company`` setting. - - """ - put_url = 'tasks/companies/{0}/teams/{1}/tasks/{2}'.format( - company_id, team_id, quote(str(code))) - data = {'code': code, - 'description': description, - 'url': url} - - if engagements: - engagements = self._encode_task_codes(engagements) - data['engagements'] = engagements - - if all_in_company: - data['all_in_company'] = 1 - - result = self.put(put_url, data) - return result - - def put_company_task(self, company_id, code, description, url, - engagements=None, all_in_company=None): - """ - Update an activity within a company. - This call is identical to ``put_team_task`` so that - ``team_id`` is equal to ``company_id``. - - The authenticated user needs to have hiring manager privileges. - - *Parameters:* - :company_id: Company ID. Use the ``parent_team__id`` value - from ``hr.get_team()`` API call. - - :code: Task code - - :description: Task description - - :url: Task URL - - :engagements: (optional) A list of engagements - that are to be assigned to the created activity. - It can be a single engagement ID, - or an iterable of IDs. - - :all_in_company: (optional) If ``True``, assign the - created activity to all engagements - that are exist in the company at - the moment. - - If both ``engagements`` and ``all_in_company`` are provided, - ``engagements`` list will override the ``all_in_company`` setting. - - """ - team_id = company_id - return self.put_team_task( - company_id, team_id, code, - description, url, engagements=engagements, - all_in_company=all_in_company) - - def archive_team_task(self, company_id, team_id, task_code): - """Archive single activity within a team. - - *Parameters:* - :company_id: Company ID. Use the ``parent_team__id`` value - from ``hr.get_team()`` API call. - - :team_id: Team ID. Use the 'id' value - from ``hr.get_team()`` API call. - - :task_code: A single Activity ID as a string - or a list or tuple of IDs. - - """ - task_code = self._encode_task_codes(task_code) - - url = 'tasks/companies/{0}/teams/{1}/archive/{2}'.format( - company_id, team_id, quote(task_code)) - return self.put(url, data={}) - - def archive_company_task(self, company_id, task_code): - """Archive single activity within a company. - - This call is identical to ``archive_team_task``, so that - ``team_id`` is the same as ``company_id``. - - *Parameters:* - :company_id: Company ID. Use the ``parent_team__id`` value - from ``hr.get_team()`` API call. - - :task_code: A single Activity ID as a string - or a list or tuple of IDs. - - """ - team_id = company_id - return self.archive_team_task(company_id, team_id, task_code) - - def unarchive_team_task(self, company_id, team_id, task_code): - """Unarchive single activity within a team. - - *Parameters:* - :company_id: Company ID. Use the ``parent_team__id`` value - from ``hr.get_team()`` API call. - - :team_id: Team ID. Use the 'id' value - from ``hr.get_team()`` API call. - - :task_code: A single Activity ID as a string - or a list or tuple of IDs. - - """ - task_code = self._encode_task_codes(task_code) - - url = 'tasks/companies/{0}/teams/{1}/unarchive/{2}'.format( - company_id, team_id, quote(task_code)) - return self.put(url, data={}) - - def unarchive_company_task(self, company_id, task_code): - """Unarchive single activity within a company. - - This call is identical to ``unarchive_team_task``, so that - ``team_id`` is the same as ``company_id``. - - *Parameters:* - :company_id: Company ID. Use the ``parent_team__id`` value - from ``hr.get_team()`` API call. - - :task_code: A single Activity ID as a string - or a list or tuple of IDs. - - """ - team_id = company_id - return self.unarchive_team_task(company_id, team_id, task_code) - - def assign_engagement(self, company_id, team_id, - engagement, task_codes=None): - """Assign an existing engagement to the list of activities. - - Note that activity will appear in contractor's team client - only if his engagement is assigned to the activity and - activities are activated for the ongoing contract. - - This will override assigned engagements for the given activities. - For example, if you pass empty ``task_codes`` or just omit - this parameter, contractor engagement will be unassigned from - all Activities. - - *Parameters:* - :company_id: Company ID. Use the ``parent_team__id`` value - from ``hr.get_team()`` API call. - - :team_id: Team ID. Use the 'id' value - from ``hr.get_team()`` API call. - - :engagement: Engagement ID that will be assigned/unassigned - to the given list of Activities. - - :task_codes: Task codes (must be a list, even of 1 item) - - """ - task_codes = self._encode_task_codes(task_codes) - url = 'tasks/companies/{0}/teams/{1}/engagements/{2}/tasks'.format( - company_id, team_id, engagement) - data = {'tasks': task_codes} - result = self.put(url, data) - return result - - def update_batch_tasks(self, company_id, csv_data): - """ - Batch update Activities using csv file contents. - - This call is experimental, use it on your own risk. - - *Parameters:* - :company_id: Company ID. Use the ``parent_team__id`` value - from ``hr.get_team()`` API call. - - :csv_data: Task records in csv format but with "
" - as line separator - - "companyid","teamid","userid","taskid","description","url" - Example: - "acmeinc","","","T1","A Task","http://example.com"
- "acmeinc","acmeinc:dev","b42","T2","Task 2","" - - """ - data = {'data': csv_data} - url = 'tasks/companies/{0}/tasks/batch'.format(company_id) - - return self.put(url, data) - - -class Task_V2(Namespace): - api_url = 'tasks/' - version = 2 - - def _encode_task_codes(self, task_codes): - if isinstance(task_codes, (list, tuple)): - return ';'.join(str(c) for c in task_codes) - else: - return str(task_codes) - - def list_engagement_activities(self, engagement_ref): - """ - Retrieve list of all activities assigned to the specific engagement. - - The user authenticated must have been granted the appropriate - hiring manager permissions. - - *Parameters:* - :engagement_ref: Engagement reference ID. You can get it using - 'List engagemnets' API call. Example: `1234`. - - """ - url = 'tasks/contracts/{0}'.format(engagement_ref) - result = self.get(url) - return result - - def assign_to_engagement(self, engagement_ref, task_codes=None): - """Assign a list of activities to the existing engagement. - - Note that activity will appear in contractor's team client - only if his engagement is assigned to the activity and - activities are activated for the ongoing contract. - - This will override assigned engagements for the given activities. - For example, if you pass empty ``task_codes`` or just omit - this parameter, contractor engagement will be unassigned from - all Activities. - - *Parameters:* - :engagement_ref: Engagement ID that will be assigned/unassigned - to the given list of Activities. - - :task_codes: Task codes (must be a list, even of 1 item) - - """ - task_codes = self._encode_task_codes(task_codes) - url = 'tasks/contracts/{0}'.format(engagement_ref) - data = {'tasks': task_codes} - result = self.put(url, data) - return result diff --git a/upwork/routers/team.py b/upwork/routers/team.py deleted file mode 100644 index 222abb0..0000000 --- a/upwork/routers/team.py +++ /dev/null @@ -1,221 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2018 Upwork - -from upwork.namespaces import Namespace -from upwork.utils import assert_parameter - - -class Team_V3(Namespace): - - api_url = 'team/' - version = 3 - - def get_workdays_by_company(self, company_id, from_date, till_date, offset=None): - """ - Retrieve workdays by company - - *Parameters:* - :company_id: The Company ID. - - :from_date: The target start date in `yyyymmdd` format. - - :end_date: The target end date in `yyyymmdd` format. - - :offset: (optional) Time zone offset. - - """ - url = 'workdays/companies/{0}/{1},{2}'.format(company_id, from_date, till_date) - - data = {} - - if offset: - data['offset'] = offset - - result = self.get(url, data) - if 'error' in result: - return result - - workdays = result.get('workdays', data) - if not isinstance(workdays, list): - workdays = {} - - return workdays - - def get_workdays_by_contract(self, contract_id, from_date, till_date, offset=None): - """ - Retrieve workdays by contract - - *Parameters:* - :contract_id: The Contract ID. - - :from_date: The target start date in `yyyymmdd` format. - - :end_date: The target end date in `yyyymmdd` format. - - :offset: (optional) Time zone offset. - - """ - url = 'workdays/contracts/{0}/{1},{2}'.format(contract_id, from_date, till_date) - - data = {} - - if offset: - data['offset'] = offset - - result = self.get(url, data) - if 'error' in result: - return result - - workdays = result.get('workdays', data) - if not isinstance(workdays, list): - workdays = {} - - return workdays - - def get_workdiaries(self, team_id, date, sort_by=None, activity=None, freelancer=None, paging=None): - """ - Retrieve a team member's workdiaries for given date or today. - *Parameters:* - :team_id: The Team ID. - - :date: A datetime object or a string in yyyymmdd format. - - :sort_by: (optional) Sort parameter. - - :activity: (optional) Activity. - - :freelancer: (optional) Freelancer filter. - - :paging: (optional) Paging. - - """ - url = 'workdiaries/companies/{0}/{1}'.format(team_id, date) - data = {} - - if sort_by: - data['sort_by'] = sort_by - - if activity: - data['activity'] = activity - - if freelancer: - data['freelancer'] = freelancer - - if paging: - data['paging'] = paging - - result = self.get(url, data) - if 'error' in result: - return result - - snapshots = result.get('data', data) - if not isinstance(snapshots, list): - snapshots = [snapshots] - #not sure we need to return user - return snapshots - - def get_workdiaries_by_contract(self, contract_id, date, offset=None): - """ - Retrieve workdiary snapshots by contract - - *Parameters:* - :contract_id: The Contract ID. - - :date: The target date in `yyyymmdd` format. - - :offset: (optional) Time zone offset. - - """ - url = 'workdiaries/contracts/{0}/{1}'.format(contract_id, date) - - data = {} - - if offset: - data['offset'] = offset - - result = self.get(url, data) - if 'error' in result: - return result - - snapshots = result.get('data', data).get('data', []) - if not isinstance(snapshots, list): - snapshots = [snapshots] - - return snapshots - - def get_snapshot_by_contract(self, contract_id, datetime): - """ - Retrieve a company's user snapshots by contract ID during given time. - - *Parameters:* - :contract_id: The Contract ID - - :datetime: Timestamp either a datetime object - or a string with UNIX timestamp (number of - seconds after epoch) - - """ - url = 'snapshots/contracts/{0}/{1}'.format(contract_id, datetime) - - result = self.get(url) - if 'snapshot' in result: - snapshot = result['snapshot'] - else: - snapshot = [] - if 'error' in result: - return result - return snapshot - - def update_snapshot_by_contract(self, contract_id, datetime, memo, task, task_desc): - """ - Update a company's user snapshot memo by contract ID at given time. - - *Parameters:* - :contract_id: The Contract ID - - :datetime: Timestamp either a datetime object - or a string with UNIX timestamp (number of - seconds after epoch) - - More than one timestamps can be specified either - as a range or as a list of values: - - - list: use the semicolon character (;) e.g. - ``20081205T090351Z;20081405T090851Z;20081705T091853Z`` - - :memo: The Memo text - - :task: Task ID - - :task_desc: Task description - - """ - url = 'snapshots/contracts/{0}/{1}'.format(contract_id, datetime) - - return self.put(url, {'memo': memo, 'task': task, 'task_desc': task_desc}) - - def delete_snapshot_by_contract(self, contract_id, datetime, only_webcam=None): - """ - Delete a company's user snapshot by contract ID at given time. - - *Parameters:* - :contract_id: The Contract ID - - :datetime: Timestamp either a datetime object - or a string with UNIX timestamp (number of - seconds after epoch) - - More than one timestamps can be specified either - as a range or as a list of values: - - - list: use the semicolon character (;) e.g. - 20081205T090351Z;20081405T090851Z;20081705T091853Z - - :only_webcam: Delete only webcam image - - """ - url = 'snapshots/contracts/{0}/{1}'.format(contract_id, datetime) - if only_webcam: - url = '{0}/{1}'.format(url, '/webcam') - return self.delete(url) diff --git a/upwork/routers/timereport.py b/upwork/routers/timereport.py deleted file mode 100644 index 3bb3c95..0000000 --- a/upwork/routers/timereport.py +++ /dev/null @@ -1,103 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork - -from upwork.namespaces import GdsNamespace - - -class TimeReport(GdsNamespace): - api_url = 'timereports/' - version = 1 - - def get_provider_report(self, provider_id, query, hours=False): - """ - Get caller's specific time report. - - The caller of this API must be the provider himself. - - *Parameters:* - :provider_id: The provider_id of the caller - - :query: The GDS query string - - :hours: (optional) Limits the query to hour specific elements - and hides all financial details - Default: ``False`` - """ - url = 'providers/{0}'.format(provider_id) - if hours: - url = '{0}/hours'.format(url) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result - - def get_company_report(self, company_id, query, hours=False): - """ - Generate company wide time reports. - - All reporting fields available except earnings related fields. - In order to access this API the authorized user needs either hiring - or finance permissions to all teams within the company. - - *Parameters:* - :company_id: Company ID - - :query: The GDS query string - - :hours: (optional) Limits the query to hour specific elements - and hides all financial details - Default: ``False`` - - """ - url = 'companies/{0}'.format(company_id) - if hours: - url = '{0}/hours'.format(url) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result - - def get_team_report(self, company_id, team_id, query, hours=False): - """ - Generate team specific time reports. - - *Parameters:* - :company_id: The Company ID - - :team_id: The Team ID - - :query: The GDS query string - - :hours: (optional) Limits the query to hour specific elements - and hides all financial details - Default: ``False`` - - """ - url = 'companies/{0}/teams/{1}'.format(company_id, team_id) - if hours: - url = '{0}/hours'.format(url) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result - - def get_agency_report(self, company_id, agency_id, query, hours=False): - """ - Generate agency specific time reports. - - *Parameters:* - :company_id: The Company ID - - :agency_id: The Agency ID - - :query: The GDS query string - - :hours: (optional) Limits the query to hour specific elements - and hides all financial details - Default: ``False`` - - """ - url = 'companies/{0}/agencies/{1}'.format(company_id, agency_id) - if hours: - url = '{0}/hours'.format(url) - tq = str(query) - result = self.get(url, data={'tq': tq}) - return result diff --git a/upwork/tests.py b/upwork/tests.py deleted file mode 100644 index cd8aed4..0000000 --- a/upwork/tests.py +++ /dev/null @@ -1,1476 +0,0 @@ -# -*- coding: utf-8 -*- - -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2018 Upwork - -from decimal import Decimal - - -from upwork import Client -from upwork import utils -from upwork.exceptions import (HTTP400BadRequestError, - HTTP401UnauthorizedError, - HTTP403ForbiddenError, - HTTP404NotFoundError, - ApiValueError, - IncorrectJsonResponseError) - -from upwork.namespaces import Namespace -from upwork.oauth import OAuth -from upwork.routers.team import Team_V3 -from upwork.http import UPWORK_ERROR_CODE, UPWORK_ERROR_MESSAGE - -from nose.tools import eq_, ok_ -from mock import Mock, patch -from upwork.compatibility import HTTPError, httplib, urlparse - -try: - import json -except ImportError: - import simplejson as json - - -class MicroMock(object): - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - - -sample_json_dict = {u'glossary': - {u'GlossDiv': - {u'GlossList': - {u'GlossEntry': - {u'GlossDef': - {u'GlossSeeAlso': [u'GML', u'XML'], - u'para': u'A meta-markup language'}, - u'GlossSee': u'markup', - u'Acronym': u'SGML', - u'GlossTerm': u'Standard Generalized Markup Language', - u'Abbrev': u'ISO 8879:1986', - u'SortAs': u'SGML', - u'ID': u'SGML'}}, - u'title': u'S'}, - u'title': u'example glossary'}} - - -def patched_urlopen(*args, **kwargs): - return MicroMock(data=json.dumps(sample_json_dict), status=200) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen) -def test_client_urlopen(): - public_key = 'public' - secret_key = 'secret' - - client = Client(public_key, secret_key, - oauth_access_token='some access token', - oauth_access_token_secret='some access token secret') - - #test urlopen - data = [{'url': 'http://test.url', - 'data': {'foo': 'bar'}, - 'method': 'GET', - 'result_data': None, - 'result_url': 'http://test.url?api_sig=ddbf4b10a47ca8300554441dc7c9042b&api_key=public&foo=bar', - 'result_method': 'GET'}, - {'url': 'http://test.url', - 'data': {}, - 'method': 'POST', - 'result_data': 'api_sig=ba343f176db8166c4b7e88911e7e46ec&api_key=public', - 'result_url': 'http://test.url', - 'result_method': 'POST'}, - {'url': 'http://test.url', - 'data': {}, - 'method': 'PUT', - 'result_data': 'api_sig=52cbaea073a5d47abdffc7fc8ccd839b&api_key=public&http_method=put', - 'result_url': 'http://test.url', - 'result_method': 'POST'}, - {'url': 'http://test.url', - 'data': {}, - 'method': 'DELETE', - 'result_data': 'api_sig=8621f072b1492fbd164d808307ba72b9&api_key=public&http_method=delete', - 'result_url': 'http://test.url', - 'result_method': 'POST'}, - ] - - result_json = json.dumps(sample_json_dict) - - for params in data: - result = client.urlopen(url=params['url'], - data=params['data'], - method=params['method']) - assert result.data == result_json, (result.data, result_json) - - -def patched_urlopen_error(method, url, code=httplib.BAD_REQUEST, - message=None, data=None, **kwargs): - getheaders = Mock() - getheaders.return_value = {UPWORK_ERROR_CODE: code, - UPWORK_ERROR_MESSAGE: message} - return MicroMock(data=data, getheaders=getheaders, status=code) - - -def patched_urlopen_incorrect_json(self, method, url, **kwargs): - return patched_urlopen_error( - method, url, code=httplib.OK, data='Service temporarily unavailable') - - -def patched_urlopen_400(self, method, url, **kwargs): - return patched_urlopen_error( - method, url, code=httplib.BAD_REQUEST, - message='Limit exceeded', **kwargs) - - -def patched_urlopen_401(self, method, url, **kwargs): - return patched_urlopen_error( - method, url, code=httplib.UNAUTHORIZED, - message='Not authorized', **kwargs) - - -def patched_urlopen_403(self, method, url, **kwargs): - return patched_urlopen_error( - method, url, code=httplib.FORBIDDEN, - message='Forbidden', **kwargs) - - -def patched_urlopen_404(self, method, url, **kwargs): - return patched_urlopen_error( - method, url, code=httplib.NOT_FOUND, - message='Not found', **kwargs) - - -def patched_urlopen_500(self, method, url, **kwargs): - return patched_urlopen_error( - method, url, code=httplib.INTERNAL_SERVER_ERROR, - message='Internal server error', **kwargs) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_incorrect_json) -def client_read_incorrect_json(client, url): - return client.read(url) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_400) -def client_read_400(client, url): - return client.read(url) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_401) -def client_read_401(client, url): - return client.read(url) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_403) -def client_read_403(client, url): - return client.read(url) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_404) -def client_read_404(client, url): - return client.read(url) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_500) -def client_read_500(client, url): - return client.read(url) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen) -def test_client_read(): - """Test client read() method. - - Test cases: - method default (get) - other we already tested - - format json|yaml ( should produce error) - - codes 200|400|401|403|404|500 - - """ - public_key = 'public' - secret_key = 'secret' - - client = Client(public_key, secret_key, - oauth_access_token='some access token', - oauth_access_token_secret='some access token secret') - test_url = 'http://test.url' - - # Produce error on format other then json - class NotJsonException(Exception): - pass - - try: - client.read(url=test_url, format='yaml') - raise NotJsonException("Client.read() doesn't produce error on " - "yaml format") - except NotJsonException as e: - raise e - except Exception: - pass - - # Test get, all ok - result = client.read(url=test_url) - assert result == sample_json_dict, result - - # Test get and status is ok, but json is incorrect, - # IncorrectJsonResponseError should be raised - try: - result = client_read_incorrect_json(client=client, url=test_url) - ok_(0, "No exception raised for 200 code and " - "incorrect json response: {0}".format(result)) - except IncorrectJsonResponseError: - pass - except Exception as e: - assert 0, "Incorrect exception raised for 200 code " \ - "and incorrect json response: " + str(e) - - # Test get, 400 error - try: - result = client_read_400(client=client, url=test_url) - except HTTP400BadRequestError as e: - pass - except Exception as e: - assert 0, "Incorrect exception raised for 400 code: " + str(e) - - # Test get, 401 error - try: - result = client_read_401(client=client, url=test_url) - except HTTP401UnauthorizedError as e: - pass - except Exception as e: - assert 0, "Incorrect exception raised for 401 code: " + str(e) - - # Test get, 403 error - try: - result = client_read_403(client=client, url=test_url) - except HTTP403ForbiddenError as e: - pass - except Exception as e: - assert 0, "Incorrect exception raised for 403 code: " + str(e) - - # Test get, 404 error - try: - result = client_read_404(client=client, url=test_url) - except HTTP404NotFoundError as e: - pass - except Exception as e: - assert 0, "Incorrect exception raised for 404 code: " + str(e) - - # Test get, 500 error - try: - result = client_read_500(client=client, url=test_url) - except HTTPError as e: - if e.code == httplib.INTERNAL_SERVER_ERROR: - pass - else: - assert 0, "Incorrect exception raised for 500 code: " + str(e) - except Exception as e: - assert 0, "Incorrect exception raised for 500 code: " + str(e) - - -def get_client(): - public_key = 'public' - secret_key = 'secret' - oauth_access_token = 'some token' - oauth_access_token_secret = 'some token secret' - return Client(public_key, secret_key, - oauth_access_token, - oauth_access_token_secret) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen) -def test_client(): - c = get_client() - test_url = "http://test.url" - - result = c.get(test_url) - assert result == sample_json_dict, result - - result = c.post(test_url) - assert result == sample_json_dict, result - - result = c.put(test_url) - assert result == sample_json_dict, result - - result = c.delete(test_url) - assert result == sample_json_dict, result - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen) -def test_namespace(): - ns = Namespace(get_client()) - test_url = "http://test.url" - - #test full_url - full_url = ns.full_url('test') - assert full_url == 'https://www.upwork.com/api/Nonev1/test', full_url - - result = ns.get(test_url) - assert result == sample_json_dict, result - - result = ns.post(test_url) - assert result == sample_json_dict, result - - result = ns.put(test_url) - assert result == sample_json_dict, result - - result = ns.delete(test_url) - assert result == sample_json_dict, result - - -teamrooms_dict = {'teamrooms': - {'teamroom': - {u'team_ref': u'1', - u'name': u'Upwork', - u'recno': u'1', - u'parent_team_ref': u'1', - u'company_name': u'Upwork', - u'company_recno': u'1', - u'teamroom_api': u'/api/team/v1/teamrooms/upwork:some.json', - u'id': u'upwork:some'}}, - 'teamroom': {'snapshot': 'test snapshot'}, - 'snapshots': {'user': 'test', 'snapshot': 'test'}, - 'data': {'user': 'test', 'snapshot': 'test'}, - 'snapshot': {'status': 'private'} - } - - -def patched_urlopen_team(*args, **kwargs): - return MicroMock(data=json.dumps(teamrooms_dict), status=200) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_team) -def test_team(): - te_v3 = Team_V3(get_client()) - - #test full_url - full_url = te_v3.full_url('test') - assert full_url == 'https://www.upwork.com/api/team/v3/test', full_url - - #test get_snapshot by contract - assert te_v3.get_snapshot_by_contract(1, 1) == teamrooms_dict['snapshot'], \ - te_v3.get_snapshot_by_contract(1, 1) - - #test update_snapshot by contract - assert te_v3.update_snapshot_by_contract(1, 1, memo='memo', task='1234', task_desc='desc') == teamrooms_dict, \ - te_v3.update_snapshot_by_contract(1, 1, memo='memo', task='1234', task_desc='desc') - - #test delete_snapshot by contract - assert te_v3.delete_snapshot_by_contract(1, 1) == teamrooms_dict, \ - te_v3.delete_snapshot_by_contract(1, 1) - - #test get_workdiaries - eq_(te_v3.get_workdiaries(1, 1), [{'snapshot':'test', 'user':'test'}]) - - #test get_workdiaries_by_contract - eq_(te_v3.get_workdiaries_by_contract(1, 1), []) - - #test get_workdays - eq_(te_v3.get_workdays_by_company(1, 1, 1), {}) - - #test get_workdays_by_contract - eq_(te_v3.get_workdays_by_contract(1, 1, 1), {}) - - #test get_snapshot_by_contract - eq_(te_v3.get_snapshot_by_contract(1, 1), {'status':'private'}) - - -teamrooms_dict_none = {'teamrooms': '', - 'teamroom': '', - 'snapshots': '', - 'snapshot': '' - } - - -def patched_urlopen_teamrooms_none(*args, **kwargs): - return MicroMock(data=json.dumps(teamrooms_dict_none), status=200) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_teamrooms_none) -def test_teamrooms_none(): - te_v3 = Team_V3(get_client()) - - #test full_url - full_url = te_v3.full_url('test') - assert full_url == 'https://www.upwork.com/api/team/v3/test', full_url - - -userroles = {u'userrole': - [{u'parent_team__reference': u'1', - u'user__id': u'testuser', u'team__id': u'test:t', - u'reference': u'1', u'team__name': u'te', - u'company__reference': u'1', - u'user__reference': u'1', - u'user__first_name': u'Test', - u'user__last_name': u'Development', - u'parent_team__id': u'testdev', - u'team__reference': u'1', u'role': u'manager', - u'affiliation_status': u'none', u'engagement__reference': u'', - u'parent_team__name': u'TestDev', u'has_team_room_access': u'1', - u'company__name': u'Test Dev', - u'permissions': - {u'permission': [u'manage_employment', u'manage_recruiting']}}]} - -engagement = {u'status': u'active', - u'buyer_team__reference': u'1', u'provider__reference': u'2', - u'job__title': u'development', u'roles': {u'role': u'buyer'}, - u'reference': u'1', u'engagement_end_date': u'', - u'fixed_price_upfront_payment': u'0', - u'fixed_pay_amount_agreed': u'1.00', - u'provider__id': u'test_provider', - u'buyer_team__id': u'testteam:aa', - u'engagement_job_type': u'fixed-price', - u'job__reference': u'1', u'provider_team__reference': u'', - u'engagement_title': u'Developer', - u'fixed_charge_amount_agreed': u'0.01', - u'created_time': u'0000', u'provider_team__id': u'', - u'offer__reference': u'', - u'engagement_start_date': u'000', u'description': u''} - -engagements = {u'lister': - {u'total_items': u'10', u'query': u'', - u'paging': {u'count': u'10', u'offset': u'0'}, u'sort': u''}, - u'engagement': [engagement, engagement], - } - -offer = {u'provider__reference': u'1', - u'signed_by_buyer_user': u'', - u'reference': u'1', u'job__description': u'python', - u'buyer_company__name': u'Python community', - u'engagement_title': u'developer', u'created_time': u'000', - u'buyer_company__reference': u'2', u'buyer_team__id': u'testteam:aa', - u'interview_status': u'in_process', u'buyer_team__reference': u'1', - u'signed_time_buyer': u'', u'has_buyer_signed': u'', - u'signed_time_provider': u'', u'created_by': u'testuser', - u'job__reference': u'2', u'engagement_start_date': u'00000', - u'fixed_charge_amount_agreed': u'0.01', u'provider_team__id': u'', - u'status': u'', u'signed_by_provider_user': u'', - u'engagement_job_type': u'fixed-price', u'description': u'', - u'provider_team__name': u'', u'fixed_pay_amount_agreed': u'0.01', - u'candidacy_status': u'active', u'has_provider_signed': u'', - u'message_from_provider': u'', u'my_role': u'buyer', - u'key': u'~~0001', u'message_from_buyer': u'', - u'buyer_team__name': u'Python community 2', - u'engagement_end_date': u'', u'fixed_price_upfront_payment': u'0', - u'created_type': u'buyer', u'provider_team__reference': u'', - u'job__title': u'translation', u'expiration_date': u'', - u'engagement__reference': u''} - -offers = {u'lister': - {u'total_items': u'10', u'query': u'', u'paging': - {u'count': u'10', u'offset': u'0'}, u'sort': u''}, - u'offer': [offer, offer]} - -job = {u'subcategory2': u'Development', u'reference': u'1', - u'buyer_company__name': u'Python community', - u'job_type': u'fixed-price', u'created_time': u'000', - u'created_by': u'test', u'duration': u'', - u'last_candidacy_access_time': u'', - u'category2': u'Web', - u'buyer_team__reference': u'169108', u'title': u'translation', - u'buyer_company__reference': u'1', u'num_active_candidates': u'0', - u'buyer_team__name': u'Python community 2', u'start_date': u'000', - u'status': u'filled', u'num_new_candidates': u'0', - u'description': u'test', u'end_date': u'000', - u'public_url': u'http://www.upwork.com/jobs/~~0001', - u'visibility': u'invite-only', u'buyer_team__id': u'testteam:aa', - u'num_candidates': u'1', u'budget': u'1000', u'cancelled_date': u'', - u'filled_date': u'0000'} - -jobs = [job, job] - -task = {u'reference': u'test', u'company_reference': u'1', - u'team__reference': u'1', u'user__reference': u'1', - u'code': u'1', u'description': u'test task', - u'url': u'http://url.upwork.com/task', u'level': u'1'} - -tasks = [task, task] - -auth_user = {u'first_name': u'TestF', u'last_name': u'TestL', - u'uid': u'testuser', u'timezone_offset': u'0', - u'timezone': u'Europe/Athens', u'mail': u'test_user@upwork.com', - u'messenger_id': u'', u'messenger_type': u'yahoo'} - -user = {u'status': u'active', u'first_name': u'TestF', - u'last_name': u'TestL', u'reference': u'0001', - u'timezone_offset': u'10800', - u'public_url': u'http://www.upwork.com/users/~~000', - u'is_provider': u'1', - u'timezone': u'GMT+02:00 Athens, Helsinki, Istanbul', - u'id': u'testuser'} - -team = {u'status': u'active', u'parent_team__reference': u'0', - u'name': u'Test', - u'reference': u'1', - u'company__reference': u'1', - u'id': u'test', - u'parent_team__id': u'test_parent', - u'company_name': u'Test', u'is_hidden': u'', - u'parent_team__name': u'Test parent'} - -company = {u'status': u'active', - u'name': u'Test', - u'reference': u'1', - u'company_id': u'1', - u'owner_user_id': u'1', } - -hr_dict = {u'auth_user': auth_user, - u'server_time': u'0000', - u'user': user, - u'team': team, - u'company': company, - u'teams': [team, team], - u'companies': [company, company], - u'users': [user, user], - u'tasks': task, - u'userroles': userroles, - u'engagements': engagements, - u'engagement': engagement, - u'offer': offer, - u'offers': offers, - u'job': job, - u'jobs': jobs} - - -def patched_urlopen_hr(*args, **kwargs): - return MicroMock(data=json.dumps(hr_dict), status=200) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_hr) -def test_get_hrv2_user(): - hr = get_client().hr - - #test get_user - assert hr.get_user(1) == hr_dict[u'user'], hr.get_user(1) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_hr) -def test_get_hrv2_companies(): - hr = get_client().hr - #test get_companies - assert hr.get_companies() == hr_dict[u'companies'], hr.get_companies() - - #test get_company - assert hr.get_company(1) == hr_dict[u'company'], hr.get_company(1) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_hr) -def test_get_hrv2_company_teams(): - hr = get_client().hr - #test get_company_teams - assert hr.get_company_teams(1) == hr_dict['teams'], hr.get_company_teams(1) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_hr) -def test_get_hrv2_company_users(): - hr = get_client().hr - #test get_company_users - assert hr.get_company_users(1) == hr_dict['users'], hr.get_company_users(1) - assert hr.get_company_users(1, False) == hr_dict['users'], \ - hr.get_company_users(1, False) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_hr) -def test_get_hrv2_teams(): - hr = get_client().hr - #test get_teams - assert hr.get_teams() == hr_dict[u'teams'], hr.get_teams() - - #test get_team - assert hr.get_team(1) == hr_dict[u'team'], hr.get_team(1) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_hr) -def test_get_hrv2_team_users(): - hr = get_client().hr - #test get_team_users - assert hr.get_team_users(1) == hr_dict[u'users'], hr.get_team_users(1) - assert hr.get_team_users(1, False) == hr_dict[u'users'], \ - hr.get_team_users(1, False) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_hr) -def test_get_hrv2_userroles(): - hr = get_client().hr - #test get_user_roles - assert hr.get_user_roles() == hr_dict['userroles'], hr.get_user_role() - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_hr) -def test_get_hrv2_jobs(): - hr = get_client().hr - #test get_jobs - assert hr.get_jobs(1) == hr_dict[u'jobs'], hr.get_jobs() - assert hr.get_job(1) == hr_dict[u'job'], hr.get_job(1) - result = hr.update_job(1, 2, 'title', 'desc', 'public', budget=100, - status='open') - eq_(result, hr_dict) - assert hr.delete_job(1, 41) == hr_dict, hr.delete_job(1, 41) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_hr) -def test_get_hrv2_offers(): - hr = get_client().hr - #test get_offers - assert hr.get_offers(1) == hr_dict[u'offers'], hr.get_offers() - assert hr.get_offer(1) == hr_dict[u'offer'], hr.get_offer(1) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_hr) -def test_get_hrv2_engagements(): - hr = get_client().hr - - eq_(hr.get_engagements(), hr_dict[u'engagements']) - - eq_(hr.get_engagements(provider_reference=1), hr_dict[u'engagements']) - eq_(hr.get_engagements(profile_key=1), hr_dict[u'engagements']) - eq_(hr.get_engagement(1), hr_dict[u'engagement']) - - -adjustments = {u'adjustment': {u'reference': '100'}} - - -def patched_urlopen_hradjustment(*args, **kwargs): - return MicroMock(data=json.dumps(adjustments), status=200) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_hradjustment) -def test_hrv2_post_adjustment(): - hr = get_client().hr - - # Using ``charge_amount`` - result = hr.post_team_adjustment( - 1, 2, 'a test', charge_amount=100, notes='test note') - assert result == adjustments[u'adjustment'], result - - try: - # If ``charge_amount`` is absent, - # error should be raised - hr.post_team_adjustment(1, 2, 'a test', notes='test note', charge_amount=0) - raise Exception('No error ApiValueError was raised when ``charge_amount`` is absent') - except ApiValueError: - pass - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_hr) -def test_hr_end_contract(): - hr = get_client().hr - - result = hr.end_contract(1, 'API_REAS_JOB_COMPLETED_SUCCESSFULLY', 'yes') - assert result == hr_dict, result - - # Test options validation - try: - result = hr.end_contract(1, 'reason', 'yes') - assert result == hr_dict, result - except ApiValueError: - pass - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_hr) -def test_hr_suspend_contract(): - hr = get_client().hr - - result = hr.suspend_contract(1, 'message') - assert result == hr_dict, result - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_hr) -def test_hr_restart_contract(): - hr = get_client().hr - - result = hr.restart_contract(1, 'message') - assert result == hr_dict, result - - -job_data2 = { - 'buyer_team_reference': 111, - 'title': 'Test job from API', - 'job_type': 'hourly', - 'description': 'this is test job, please do not apply to it', - 'visibility': 'upwork', - 'category2': 'Web, Mobile & Software Dev', - 'subcategory2': 'Web & Mobile Development', - 'budget': 100, - 'duration': 10, - 'start_date': 'some start date', - 'skills': ['Python', 'JS'] -} - - -def patched_urlopen_job_data_parameters2(self, method, url, **kwargs): - post_dict = urlparse.parse_qs(kwargs.get('body')) - post_dict.pop('oauth_timestamp') - post_dict.pop('oauth_signature') - post_dict.pop('oauth_nonce') - eq_( - dict(post_dict.items()), - {'buyer_team__reference': ['111'], - 'category2': ['Web, Mobile & Software Dev'], - 'subcategory2': ['Web & Mobile Development'], - 'title': ['Test job from API'], - 'skills': ['Python;JS'], 'job_type': ['hourly'], - 'oauth_consumer_key': ['public'], - 'oauth_signature_method': ['HMAC-SHA1'], 'budget': ['100'], - 'visibility': ['upwork'], - 'oauth_version': ['1.0'], 'oauth_token': ['some token'], - 'oauth_body_hash': ['2jmj7l5rSw0yVb/vlWAYkK/YBwk='], - 'duration': ['10'], - 'start_date': ['some start date'], - 'description': ['this is test job, please do not apply to it']}) - return MicroMock(data='{"some":"data"}', status=200) - - -def patched_urlopen_job_data_parameters(self, method, url, **kwargs): - post_dict = urlparse.parse_qs(kwargs.get('body')) - post_dict.pop('oauth_timestamp') - post_dict.pop('oauth_signature') - post_dict.pop('oauth_nonce') - eq_( - dict(post_dict.items()), - {'category2': ['Web Development'], 'buyer_team__reference': ['111'], - 'subcategory2': ['Other - Web Development'], - 'title': ['Test job from API'], - 'skills': ['Python;JS'], 'job_type': ['hourly'], - 'oauth_consumer_key': ['public'], - 'oauth_signature_method': ['HMAC-SHA1'], 'budget': ['100'], - 'visibility': ['upwork'], - 'oauth_version': ['1.0'], 'oauth_token': ['some token'], - 'oauth_body_hash': ['2jmj7l5rSw0yVb/vlWAYkK/YBwk='], - 'duration': ['10'], - 'start_date': ['some start date'], - 'description': ['this is test job, please do not apply to it']}) - return MicroMock(data='{"some":"data"}', status=200) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_job_data_parameters2) -def test_job_data_parameters_subcategory2(): - hr = get_client().hr - hr.post_job(**job_data2) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_job_data_parameters) -def test_job_data_no_category(): - hr = get_client().hr - - try: - hr.post_job('111', 'test', 'hourly', 'descr', 'upwork') - raise Exception('Request should raise ApiValueError exception.') - except ApiValueError: - pass - -provider_dict = {'profile': - {u'response_time': u'31.0000000000000000', - u'dev_agency_ref': u'', - u'dev_adj_score_recent': u'0', - u'dev_ui_profile_access': u'Public', - u'dev_portrait': u'', - u'dev_ic': u'Freelance Provider', - u'certification': u'', - u'dev_usr_score': u'0', - u'dev_country': u'Ukraine', - u'dev_recent_rank_percentile': u'0', - u'dev_profile_title': u'Python developer', - u'dev_groups': u'', - u'dev_scores': - {u'dev_score': - [{u'description': u'competency and skills for the job, understanding of specifications/instructions', - u'avg_category_score_recent': u'', - u'avg_category_score': u'', - u'order': u'1', u'label': u'Skills'}, - {u'description': u'quality of work deliverables', - u'avg_category_score_recent': u'', - u'avg_category_score': u'', u'order': u'2', u'label': u'Quality'}, - ] - }}, - 'providers': {'test': 'test'}, - 'jobs': {'test': 'test'}, - 'otherexp': 'experiences', - 'skills': 'skills', - 'specialties': 'specialties', - 'tests': 'tests', - 'certificates': 'certificates', - 'employments': 'employments', - 'educations': 'employments', - 'projects': 'projects', - 'quick_info': 'quick_info', - 'categories': 'category 1', - 'regions': 'region 1', - 'tests': 'test 1', - } - - -def patched_urlopen_provider(*args, **kwargs): - return MicroMock(data=json.dumps(provider_dict), status=200) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_provider) -def test_provider(): - pr = get_client().provider - - #test full_url - full_url = pr.full_url('test') - assert full_url == 'https://www.upwork.com/api/profiles/v1/test', full_url - - #test get_provider - assert pr.get_provider(1) == provider_dict['profile'], pr.get_provider(1) - - #test get_provider_brief - assert pr.get_provider_brief(1) == provider_dict['profile'], \ - pr.get_provider_brief(1) - - result = pr.get_skills_metadata() - assert result == provider_dict['skills'] - - result = pr.get_specialties_metadata() - assert result == provider_dict['specialties'] - - result = pr.get_regions_metadata() - assert result == provider_dict['regions'] - - result = pr.get_tests_metadata() - assert result == provider_dict['tests'] - - -rooms_dict = {u'rooms': []} - -def patched_urlopen_rooms(*args, **kwargs): - return MicroMock(data=json.dumps(rooms_dict), status=200) - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_rooms) -def test_get_rooms(): - messages = get_client().messages - - #test full_url - full_url = messages.full_url('testcompany/rooms') - assert full_url == 'https://www.upwork.com/api/messages/v3/testcompany/rooms', full_url - - #test get_rooms - assert messages.get_rooms("testcompany") == rooms_dict, messages.get_rooms("testcompany") - -room_dict = {u'room': {}} - -def patched_urlopen_room(*args, **kwargs): - return MicroMock(data=json.dumps(room_dict), status=200) - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_room) -def test_get_room_details(): - messages = get_client().messages - - #test get_room_details - assert messages.get_room_details("testcompany", "room-id") ==\ - room_dict, messages.get_room_details("testcompany", "room-id") - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_room) -def test_get_room_messages(): - messages = get_client().messages - - #test get_room_messages - assert messages.get_room_messages("testcompany", "room-id") ==\ - room_dict, messages.get_room_messages("testcompany", "room-id") - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_room) -def test_get_room_by_offer(): - messages = get_client().messages - - #test get_room_by_offer - assert messages.get_room_by_offer("testcompany", "1234") ==\ - room_dict, messages.get_room_by_offer("testcompany", "1234") - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_room) -def test_get_room_by_application(): - messages = get_client().messages - - #test get_room_by_applications - assert messages.get_room_by_application("testcompany", "1234") ==\ - room_dict, messages.get_room_by_application("testcompany", "1234") - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_room) -def test_get_room_by_contract(): - messages = get_client().messages - - #test get_room_by_contract - assert messages.get_room_by_contract("testcompany", "1234") ==\ - room_dict, messages.get_room_by_contract("testcompany", "1234") - -read_room_content_dict = {"room": {"test": '1'}} - -def patched_urlopen_read_room_content(*args, **kwargs): - return MicroMock(data=json.dumps(read_room_content_dict), status=200) - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_read_room_content) -def test_create_room(): - messages = get_client().messages - - message = messages.create_room('testcompany', {'roomName': 'test room'}) - assert message == read_room_content_dict, message - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_read_room_content) -def test_send_message_to_room(): - messages = get_client().messages - - message = messages.send_message_to_room('testcompany', 'room-id', {'message': 'test message'}) - assert message == read_room_content_dict, message - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_read_room_content) -def test_update_room_settings(): - messages = get_client().messages - - message = messages.update_room_settings('testcompany', 'room-id', 'userid', {'isFavorite': 'true'}) - assert message == read_room_content_dict, message - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_read_room_content) -def test_update_room_metadata(): - messages = get_client().messages - - message = messages.update_room_metadata('testcompany', 'room-id', {'isReadOnly': 'true'}) - assert message == read_room_content_dict, message - - -timereport_dict = {u'table': - {u'rows': - [{u'c': - [{u'v': u'20100513'}, - {u'v': u'company1:team1'}, - {u'v': u'1'}, - {u'v': u'1'}, - {u'v': u'0'}, - {u'v': u'1'}, - {u'v': u'Bug 1: Test'}]}], - u'cols': - [{u'type': u'date', u'label': u'worked_on'}, - {u'type': u'string', u'label': u'assignment_team_id'}, - {u'type': u'number', u'label': u'hours'}, - {u'type': u'number', u'label': u'earnings'}, - {u'type': u'number', u'label': u'earnings_offline'}, - {u'type': u'string', u'label': u'task'}, - {u'type': u'string', u'label': u'memo'}]}} - - -def patched_urlopen_timereport_content(*args, **kwargs): - return MicroMock(data=json.dumps(timereport_dict), status=200) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_timereport_content) -def test_get_provider_timereport(): - tc = get_client().timereport - - read = tc.get_provider_report('test',\ - utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == timereport_dict, read - - read = tc.get_provider_report('test',\ - utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1)), - hours=True) - assert read == timereport_dict, read - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_timereport_content) -def test_get_company_timereport(): - tc = get_client().timereport - - read = tc.get_company_report('test',\ - utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == timereport_dict, read - - read = tc.get_company_report('test',\ - utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1)), - hours=True) - assert read == timereport_dict, read - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_timereport_content) -def test_get_agency_timereport(): - tc = get_client().timereport - - read = tc.get_agency_report('test', 'test',\ - utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == timereport_dict, read - - read = tc.get_agency_report('test', 'test',\ - utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1)), - hours=True) - assert read == timereport_dict, read - -fin_report_dict = {u'table': - {u'rows': - [{u'c': - [{u'v': u'20100513'}, - {u'v': u'upwork:upworkps'}, - {u'v': u'1'}, - {u'v': u'1'}, - {u'v': u'0'}, - {u'v': u'1'}, - {u'v': u'Bug 1: Test'}]}], - u'cols': - [{u'type': u'date', u'label': u'worked_on'}, - {u'type': u'string', u'label': u'assignment_team_id'}, - {u'type': u'number', u'label': u'hours'}, - {u'type': u'number', u'label': u'earnings'}, - {u'type': u'number', u'label': u'earnings_offline'}, - {u'type': u'string', u'label': u'task'}, - {u'type': u'string', u'label': u'memo'}]}} - - -def patched_urlopen_fin_report_content(*args, **kwargs): - return MicroMock(data=json.dumps(fin_report_dict), status=200) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_fin_report_content) -def test_get_provider_billings(): - fr = get_client().finreport - - read = fr.get_provider_billings('test', utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == fin_report_dict, read - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_fin_report_content) -def test_get_provider_teams_billings(): - fr = get_client().finreport - - read = fr.get_provider_teams_billings('test', utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == fin_report_dict, read - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_fin_report_content) -def test_get_provider_companies_billings(): - fr = get_client().finreport - - read = fr.get_provider_companies_billings('test', utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == fin_report_dict, read - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_fin_report_content) -def test_get_provider_earnings(): - fr = get_client().finreport - - read = fr.get_provider_earnings('test', utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == fin_report_dict, read - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_fin_report_content) -def test_get_provider_teams_earnings(): - fr = get_client().finreport - - read = fr.get_provider_teams_earnings('test', utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == fin_report_dict, read - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_fin_report_content) -def test_get_provider_companies_earnings(): - fr = get_client().finreport - - read = fr.get_provider_companies_earnings('test', utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == fin_report_dict, read - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_fin_report_content) -def test_get_buyer_teams_billings(): - fr = get_client().finreport - - read = fr.get_buyer_teams_billings('test', utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == fin_report_dict, read - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_fin_report_content) -def test_get_buyer_companies_billings(): - fr = get_client().finreport - - read = fr.get_buyer_companies_billings('test', utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == fin_report_dict, read - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_fin_report_content) -def test_get_buyer_teams_earnings(): - fr = get_client().finreport - - read = fr.get_buyer_teams_earnings('test', utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == fin_report_dict, read - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_fin_report_content) -def test_get_buyer_companies_earnings(): - fr = get_client().finreport - - read = fr.get_buyer_companies_earnings('test', utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == fin_report_dict, read - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_fin_report_content) -def test_get_financial_entities(): - fr = get_client().finreport - - read = fr.get_financial_entities('test', utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == fin_report_dict, read - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_fin_report_content) -def test_get_financial_entities_provider(): - fr = get_client().finreport - - read = fr.get_financial_entities_provider('test', utils.Query(select=['1', '2', '3'], where=(utils.Q('2') > 1))) - assert read == fin_report_dict, read - - -task_dict = {u'tasks': 'task1'} - -def patched_urlopen_task(*args, **kwargs): - return MicroMock(data=json.dumps(task_dict), status=200) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_task) -def test_get_team_tasks(): - task = get_client().task - - assert task.get_team_tasks(1, 1) == task_dict, \ - task.get_team_tasks(1, 1) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_task) -def test_get_company_tasks(): - task = get_client().task - - assert task.get_company_tasks(1) == task_dict, \ - task.get_company_tasks(1) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_task) -def test_get_team_specific_tasks(): - task = get_client().task - - assert task.get_team_specific_tasks(1, 1, [1, 1]) == task_dict['tasks'], \ - task.get_team_specific_tasks(1, 1, [1, 1]) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_task) -def test_get_company_specific_tasks(): - task = get_client().task - - assert task.get_company_specific_tasks(1, [1, 1]) == task_dict['tasks'], \ - task.get_company_specific_tasks(1, [1, 1]) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_task) -def test_post_team_task(): - task = get_client().task - - assert task.post_team_task(1, 1, 1, '1', 'ttt', - engagements=[1, 2], - all_in_company=True) == task_dict, \ - task.post_team_task(1, 1, 1, '1', 'ttt', engagements=[1, 2], - all_in_company=True) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_task) -def test_post_company_task(): - task = get_client().task - - assert task.post_company_task(1, 1, '1', 'ttt', - engagements=[1, 2], - all_in_company=True) == task_dict, \ - task.post_company_task(1, 1, '1', 'ttt') - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_task) -def test_put_team_task(): - task = get_client().task - - assert task.put_team_task(1, 1, 1, '1', 'ttt', - engagements=[1, 2], - all_in_company=True) == task_dict, \ - task.put_team_task(1, 1, 1, '1', 'ttt', engagements=[1, 2], - all_in_company=True) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_task) -def test_put_company_task(): - task = get_client().task - - assert task.put_company_task(1, 1, '1', 'ttt', engagements=[1, 2], - all_in_company=True) == task_dict, \ - task.put_company_task(1, 1, '1', 'ttt', engagements=[1, 2], - all_in_company=True) - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_task) -def test_assign_to_engagement(): - task_v2 = get_client().task_v2 - - assert task_v2.assign_to_engagement(1, "1;2") == task_dict, \ - task_v2.task_assign_to_engagement(1, "1;2") - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_task) -def test_archive_team_task(): - task = get_client().task - - assert task.archive_team_task(1, 1, 1) == task_dict, \ - task.archive_team_task(1, 1, 1) - - # Test multiple task codes - assert task.archive_team_task(1, 1, [1, 2]) == task_dict, \ - task.archive_team_task(1, 1, [1, 2]) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_task) -def test_archive_company_task(): - task = get_client().task - - assert task.archive_company_task(1, 1) == task_dict, \ - task.archive_company_task(1, 1) - - # Test multiple task codes - assert task.archive_company_task(1, [1, 2]) == task_dict, \ - task.archive_company_task(1, [1, 2]) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_task) -def test_unarchive_team_task(): - task = get_client().task - - assert task.unarchive_team_task(1, 1, 1) == task_dict, \ - task.unarchive_team_task(1, 1, 1) - - # Test multiple task codes - assert task.unarchive_team_task(1, 1, [1, 2]) == task_dict, \ - task.unarchive_team_task(1, 1, [1, 2]) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_task) -def test_unarchive_company_task(): - task = get_client().task - - assert task.unarchive_company_task(1, 1) == task_dict, \ - task.unarchive_company_task(1, 1) - - # Test multiple task codes - assert task.unarchive_company_task(1, [1, 2]) == task_dict, \ - task.unarchive_company_task(1, [1, 2]) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_task) -def test_update_batch_tasks(): - task = get_client().task - - assert task.update_batch_tasks(1, "1;2;3") == task_dict, \ - task.update_batch_tasks(1, "1;2;3") - - -def test_gds_namespace(): - from upwork.namespaces import GdsNamespace - gds = GdsNamespace(get_client()) - - assert gds.post('test.url', {}) is None, \ - gds.post('test.url', {}) - assert gds.put('test.url', {}) is None, \ - gds.put('test.url', {}) - assert gds.delete('test.url', {}) is None, \ - gds.delete('test.url', {}) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen) -def test_gds_namespace_get(): - from upwork.namespaces import GdsNamespace - gds = GdsNamespace(get_client()) - result = gds.get('http://test.url') - assert isinstance(result, dict), type(res) - assert result == sample_json_dict, (result, sample_json_dict) - - -def setup_oauth(): - return OAuth(get_client()) - - -def test_oauth_full_url(): - oa = setup_oauth() - request_token_url = oa.full_url('oauth/token/request') - access_token_url = oa.full_url('oauth/token/access') - assert request_token_url == oa.request_token_url, request_token_url - assert access_token_url == oa.access_token_url, access_token_url - - -def patched_httplib2_request(*args, **kwargs): - return {'status': '200'},\ - 'oauth_callback_confirmed=1&oauth_token=709d434e6b37a25c50e95b0e57d24c46&oauth_token_secret=193ef27f57ab4e37' - -@patch('httplib2.Http.request', patched_httplib2_request) -def test_oauth_get_request_token(): - oa = setup_oauth() - assert oa.get_request_token() == ('709d434e6b37a25c50e95b0e57d24c46',\ - '193ef27f57ab4e37') - - -@patch('httplib2.Http.request', patched_httplib2_request) -def test_oauth_get_authorize_url(): - oa = setup_oauth() - assert oa.get_authorize_url() ==\ - 'https://www.upwork.com/services/api/auth?oauth_token=709d434e6b37a25c50e95b0e57d24c46' - assert oa.get_authorize_url('http://example.com/oauth/complete') ==\ - 'https://www.upwork.com/services/api/auth?oauth_token=709d434e6b37a25c50e95b0e57d24c46&oauth_callback=http%3A%2F%2Fexample.com%2Foauth%2Fcomplete' - -def patched_httplib2_access(*args, **kwargs): - return {'status': '200'},\ - 'oauth_token=aedec833d41732a584d1a5b4959f9cd6&oauth_token_secret=9d9cccb363d2b13e' - - -@patch('httplib2.Http.request', patched_httplib2_access) -def test_oauth_get_access_token(): - oa = setup_oauth() - oa.request_token = '709d434e6b37a25c50e95b0e57d24c46' - oa.request_token_secret = '193ef27f57ab4e37' - assert oa.get_access_token('9cbcbc19f8acc2d85a013e377ddd4118') ==\ - ('aedec833d41732a584d1a5b4959f9cd6', '9d9cccb363d2b13e') - - -job_profiles_dict = {'profiles': {'profile': [ - { - u'amount': u'', - u'as_hrs_per_week': u'0', - u'as_job_type': u'Hourly', - u'as_opening_access': u'private', - u'as_opening_recno': u'111', - u'as_opening_title': u'Review website and improve copy writing', - u'as_provider': u'111', - u'as_rate': u'$10.00', - u'as_reason': u'Job was cancelled or postponed', - u'as_reason_api_ref': u'', - u'as_reason_recno': u'72', - u'as_recno': u'1', - u'as_status': u'Closed', - u'as_to': u'11/2011', - u'as_total_charge': u'84', - u'as_total_hours': u'3.00', - u'op_desc_digest': u'Test job 1.', - u'op_description': u'Test job 1.', - u'ciphertext': u'~~111111111', - u'ui_job_profile_access': u'upwork', - u'ui_opening_status': u'Active', - u'version': u'1' - }, - { - u'amount': u'', - u'as_hrs_per_week': u'0', - u'as_job_type': u'Hourly', - u'as_opening_access': u'private', - u'as_opening_recno': u'222', - u'as_opening_title': u'Review website and improve copy writing', - u'as_provider': u'222', - u'as_rate': u'$10.00', - u'as_reason': u'Job was cancelled or postponed', - u'as_reason_api_ref': u'', - u'as_reason_recno': u'72', - u'as_recno': u'2', - u'as_status': u'Closed', - u'as_to': u'11/2011', - u'as_total_charge': u'84', - u'as_total_hours': u'3.00', - u'ciphertext': u'~~222222222', - u'op_desc_digest': u'Test job 2.', - u'op_description': u'Test job 2.', - u'ui_job_profile_access': u'upwork', - u'ui_opening_status': u'Active', - u'version': u'1' - }, -]}} - -job_profile_dict = {'profile': - { - u'amount': u'', - u'as_hrs_per_week': u'0', - u'as_job_type': u'Hourly', - u'as_opening_access': u'private', - u'as_opening_recno': u'111', - u'as_opening_title': u'Review website and improve copy writing', - u'as_provider': u'111', - u'as_rate': u'$10.00', - u'as_reason': u'Job was cancelled or postponed', - u'as_reason_api_ref': u'', - u'as_reason_recno': u'72', - u'as_recno': u'1', - u'as_status': u'Closed', - u'as_to': u'11/2011', - u'as_total_charge': u'84', - u'as_total_hours': u'3.00', - u'op_desc_digest': u'Test job 1.', - u'op_description': u'Test job 1.', - u'ciphertext': u'~~111111111', - u'ui_job_profile_access': u'upwork', - u'ui_opening_status': u'Active', - u'version': u'1' - } -} - - -def patched_urlopen_single_job(*args, **kwargs): - return MicroMock(data=json.dumps(job_profile_dict), status=200) - - -def patched_urlopen_multiple_jobs(*args, **kwargs): - return MicroMock(data=json.dumps(job_profiles_dict), status=200) - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_single_job) -def test_single_job_profile(): - job = get_client().job - - # Test full_url - full_url = job.full_url('jobs/111') - assert full_url == 'https://www.upwork.com/api/profiles/v1/jobs/111', \ - full_url - - # Test input parameters - try: - job.get_job_profile({}) - raise Exception('Request should raise ValueError exception.') - except ValueError as e: - assert 'Invalid job key' in str(e) - try: - job.get_job_profile(['~~{0}'.format(x) for x in range(21)]) - raise Exception('Request should raise ValueError exception.') - except ValueError as e: - assert 'Number of keys per request is limited' in str(e) - try: - job.get_job_profile(['~~111111', 123456]) - raise Exception('Request should raise ValueError exception.') - except ValueError as e: - assert 'List should contain only job keys not recno' in str(e) - - # Get single job profile test - assert job.get_job_profile('~~111111111') == job_profile_dict['profile'], \ - job.get_job_profile('~~111111111') - - -@patch('urllib3.PoolManager.urlopen', patched_urlopen_multiple_jobs) -def test_multiple_job_profiles(): - job = get_client().job - - # Test full_url - full_url = job.full_url('jobs/~~111;~~222') - assert full_url == \ - 'https://www.upwork.com/api/profiles/v1/jobs/~~111;~~222', full_url - - # Get multiple job profiles test - assert job.get_job_profile(['~~111111111', '~~222222222']) == \ - job_profiles_dict['profiles']['profile'], \ - job.get_job_profile(['~~111111111', '~~222222222']) - - -#====================== -# UTILS TESTS -#====================== -def test_decimal_default(): - from upwork.utils import decimal_default - - value = '0.132' - - eq_('{"value": "0.132"}', json.dumps({'value': Decimal(value)}, - default=decimal_default)) - - value = '0' - - eq_('{"value": "0"}', json.dumps({'value': Decimal(value)}, - default=decimal_default)) - - value = '10' - - eq_('{"value": "10"}', json.dumps({'value': Decimal(value)}, - default=decimal_default)) diff --git a/upwork/utils.py b/upwork/utils.py deleted file mode 100644 index 588a218..0000000 --- a/upwork/utils.py +++ /dev/null @@ -1,164 +0,0 @@ -# Python bindings to Upwork API -# python-upwork version 0.5 -# (C) 2010-2015 Upwork - -from datetime import date -from upwork.exceptions import ApiValueError - - -def assert_parameter(parameter_name, value, options_list): - """Raise an exception if parameter's value not in options list.""" - if value not in options_list: - raise ApiValueError( - "Incorrect value for {0}: '{1}', " - "valid values are {2}".format( - parameter_name, value, options_list)) - - -def decimal_default(obj): - """JSON serialization of Decimal. - - *Usage:* - ``json.dumps(data, default=decimal_default)`` - - Converts decimal to string. - - """ - if obj.__class__.__name__ == 'Decimal': - return str(obj) - raise TypeError - - -class Q(object): - """Simple GDS query constructor. - - Used to costruct :py:class:`upwork.utils.Query`. - - """ - - def __init__(self, arg1, operator=None, arg2=None): - self.arg1 = arg1 - self.operator = operator - self.arg2 = arg2 - - def __and__(self, other): - return self.__class__(self, 'AND', other) - - def __or__(self, other): - return self.__class__(self, 'OR', other) - - def __eq__(self, other): - return self.__class__(self, '=', other) - - def __lt__(self, other): - return self.__class__(self, '<', other) - - def __le__(self, other): - return self.__class__(self, '<=', other) - - def __gt__(self, other): - return self.__class__(self, '>', other) - - def __ge__(self, other): - return self.__class__(self, '>=', other) - - def arg_to_string(self, arg): - if isinstance(arg, self.__class__): - if arg.operator: - return '({0})'.format(arg) - else: - return arg - elif isinstance(arg, str): - return "'{0}'".format(arg) - elif isinstance(arg, date): - return "'{0}'".format(arg.isoformat()) - else: - return str(arg) - - def __str__(self): - if self.operator: - str1 = self.arg_to_string(self.arg1) - str2 = self.arg_to_string(self.arg2) - return '{0} {1} {2}'.format(str1, self.operator, str2) - else: - return self.arg1 - - -class Query(object): - """Simple GDS query. - - *Example:*:: - - client.timereport.get_provider_report('user1', - upwork.utils.Query(select=upwork.utils.Query.DEFAULT_TIMEREPORT_FIELDS, - where=(upwork.utils.Q('worked_on') <= date.today()) & - (upwork.utils.Q('worked_on') > '2010-05-01'))) - - """ - - DEFAULT_TIMEREPORT_FIELDS = ['worked_on', - 'team_id', - 'team_name', - 'task', - 'memo', - 'hours'] - DEFAULT_FINREPORT_FIELDS = ['reference', - 'date', - 'buyer_company__id', - 'buyer_company_name', - 'buyer_team__id', - 'buyer_team_name', - 'provider_company__id', - 'provider_company_name', - 'provider_team__id', - 'provider_team_name', - 'provider__id', - 'provider_name', - 'type', - 'subtype', - 'amount'] - - def __init__(self, select, where=None, order_by=None): - self.select = select - self.where = where - self.order_by = order_by - - def __str__(self): - select = self.select - select_str = 'SELECT ' + ', '.join(select) - where_str = '' - if self.where: - where_str = ' WHERE {0}'.format(self.where) - order_by_str = '' - if self.order_by: - order_by_str = ' ORDER BY ' + ','.join(self.order_by) - return ''.join([select_str, where_str, order_by_str]) - - -class Table(object): - - """ - A helper class to access cryptic GDS response as a list of dictionaries. - - """ - - def __init__(self, data): - self._cols = data['cols'] # Original data - self._rows = data['rows'] - self.cols = [col['label'] for col in data['cols']] - self.rows = [] - if data['rows']: - if data['rows'][0] != '': # Empty response - for row in [row['c'] for row in data['rows']]: - self.rows.append([cell['v'] for cell in row]) - - def __getitem__(self, key): - if not isinstance(key, (slice, int)): - raise TypeError - if isinstance(key, slice): - return [dict(zip(self.cols, row)) for row in self.rows[key]] - else: - return dict(zip(self.cols, self.rows[key])) - - def __len__(self): - return len(self.rows) diff --git a/upwork/web_based_app.py b/upwork/web_based_app.py deleted file mode 100644 index f4338f9..0000000 --- a/upwork/web_based_app.py +++ /dev/null @@ -1,67 +0,0 @@ -from __future__ import print_function - -import upwork -from pprint import pprint - -try: - input = raw_input -except NameError: - pass - - -def web_based_app(): - """Emulation of web-based app. - Your keys should be created with project type "Web". - - Returns: ``upwork.Client`` instance ready to work. - - """ - print("Emulating web-based app") - - public_key = input('Please enter public key: > ') - secret_key = input('Please enter secret key: > ') - - #Instantiating a client without an auth token - client = upwork.Client(public_key, secret_key) - - print("Please to this URL (authorize the app if necessary):") - print(client.auth.get_authorize_url()) - print("After that you should be redirected back to your app URL with " + \ - "additional ?oauth_verifier= parameter") - - verifier = input('Enter oauth_verifier: ') - - oauth_access_token, oauth_access_token_secret = \ - client.auth.get_access_token(verifier) - - # Instantiating a new client, now with a token. - # Not strictly necessary here (could just set `client.oauth_access_token` - # and `client.oauth_access_token_secret`), but typical for web apps, - # which wouldn't probably keep client instances between requests - client = upwork.Client(public_key, secret_key, - oauth_access_token=oauth_access_token, - oauth_access_token_secret=oauth_access_token_secret) - - return client - - -if __name__ == '__main__': - client = web_based_app() - - try: - print("My info") - pprint(client.auth.get_info()) - print("Team rooms:") - pprint(client.team.get_teamrooms()) - #HRv2 API - print("HR: companies") - pprint(client.hr.get_companies()) - print("HR: teams") - pprint(client.hr.get_teams()) - print("HR: userroles") - pprint(client.hr.get_user_roles()) - print("Get jobs") - pprint(client.provider.search_jobs({'q': 'python'})) - except Exception as e: - print("Exception at %s %s" % (client.last_method, client.last_url)) - raise e From 1783925481d12fe0c24ef2049da86343d21dad7a Mon Sep 17 00:00:00 2001 From: Maksym Novozhylov Date: Fri, 22 May 2020 15:03:02 +0200 Subject: [PATCH 2/6] v2.0.0 - Python 3 --- .coveragerc | 0 .docgen | 1 + .flake8 | 3 + .gitignore | 114 ++++++++++ .tests | 1 + .travis.yml | 19 ++ CHANGELOG.md | 4 + CONTRIBUTING.md | 23 ++ LICENSE | 176 +++++++++++++++ MANIFEST.in | 8 + README.md | 71 ++++++ example/myapp.py | 60 +++++ noxfile.py | 65 ++++++ setup.py | 36 +++ tests/__init__.py | 1 + tests/routers/__init__.py | 1 + tests/routers/activities/__init__.py | 1 + .../activities/test_activities_engagement.py | 24 ++ .../activities/test_activities_team.py | 59 +++++ .../freelancers/test_freelancers_profile.py | 15 ++ .../freelancers/test_freelancers_search.py | 9 + .../clients/test_hr_clients_applications.py | 17 ++ .../hr/clients/test_hr_clients_offers.py | 21 ++ .../test_hr_freelancers_applications.py | 15 ++ .../freelancers/test_hr_freelancers_offers.py | 23 ++ tests/routers/hr/test_hr_contracts.py | 21 ++ tests/routers/hr/test_hr_engagements.py | 15 ++ tests/routers/hr/test_hr_interviews.py | 9 + tests/routers/hr/test_hr_jobs.py | 33 +++ tests/routers/hr/test_hr_milestones.py | 51 +++++ tests/routers/hr/test_hr_roles.py | 15 ++ tests/routers/hr/test_hr_submissions.py | 25 +++ tests/routers/jobs/test_jobs_profile.py | 9 + tests/routers/jobs/test_jobs_search.py | 9 + .../test_organization_companies.py | 27 +++ .../organization/test_organization_teams.py | 15 ++ .../organization/test_organization_users.py | 15 ++ .../finance/test_reports_finance_accounts.py | 23 ++ .../finance/test_reports_finance_billings.py | 47 ++++ .../finance/test_reports_finance_earnings.py | 47 ++++ tests/routers/reports/test_reports_time.py | 51 +++++ tests/routers/test_auth.py | 9 + tests/routers/test_messages.py | 69 ++++++ tests/routers/test_metadata.py | 45 ++++ tests/routers/test_payments.py | 9 + tests/routers/test_snapshots.py | 23 ++ tests/routers/test_workdays.py | 19 ++ tests/routers/test_workiary.py | 15 ++ tests/test_client.py | 135 +++++++++++ tests/test_config.py | 17 ++ tests/test_upwork.py | 5 + upwork/__init__.py | 11 + upwork/client.py | 209 ++++++++++++++++++ upwork/config.py | 28 +++ upwork/routers/__init__.py | 20 ++ upwork/routers/activities/__init__.py | 1 + upwork/routers/activities/engagement.py | 44 ++++ upwork/routers/activities/team.py | 122 ++++++++++ upwork/routers/auth.py | 25 +++ upwork/routers/freelancers/__init__.py | 4 + upwork/routers/freelancers/profile.py | 37 ++++ upwork/routers/freelancers/search.py | 31 +++ upwork/routers/hr/__init__.py | 15 ++ upwork/routers/hr/clients/__init__.py | 1 + upwork/routers/hr/clients/applications.py | 44 ++++ upwork/routers/hr/clients/offers.py | 54 +++++ upwork/routers/hr/contracts.py | 54 +++++ upwork/routers/hr/engagements.py | 39 ++++ upwork/routers/hr/freelancers/__init__.py | 4 + upwork/routers/hr/freelancers/applications.py | 39 ++++ upwork/routers/hr/freelancers/offers.py | 52 +++++ upwork/routers/hr/interviews.py | 32 +++ upwork/routers/hr/jobs.py | 71 ++++++ upwork/routers/hr/milestones.py | 96 ++++++++ upwork/routers/hr/roles.py | 34 +++ upwork/routers/hr/submissions.py | 57 +++++ upwork/routers/jobs/__init__.py | 4 + upwork/routers/jobs/profile.py | 29 +++ upwork/routers/jobs/search.py | 31 +++ upwork/routers/messages.py | 148 +++++++++++++ upwork/routers/metadata.py | 57 +++++ upwork/routers/organization/__init__.py | 4 + upwork/routers/organization/companies.py | 49 ++++ upwork/routers/organization/teams.py | 33 +++ upwork/routers/organization/users.py | 33 +++ upwork/routers/payments.py | 32 +++ upwork/routers/reports/__init__.py | 4 + upwork/routers/reports/finance/__init__.py | 4 + upwork/routers/reports/finance/accounts.py | 50 +++++ upwork/routers/reports/finance/billings.py | 98 ++++++++ upwork/routers/reports/finance/earnings.py | 98 ++++++++ upwork/routers/reports/time.py | 109 +++++++++ upwork/routers/snapshots.py | 56 +++++ upwork/routers/workdays.py | 55 +++++ upwork/routers/workdiary.py | 47 ++++ upwork/upwork.py | 4 + 96 files changed, 3594 insertions(+) create mode 100644 .coveragerc create mode 100644 .docgen create mode 100644 .flake8 create mode 100644 .gitignore create mode 100644 .tests create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 example/myapp.py create mode 100644 noxfile.py create mode 100644 setup.py create mode 100644 tests/__init__.py create mode 100644 tests/routers/__init__.py create mode 100644 tests/routers/activities/__init__.py create mode 100644 tests/routers/activities/test_activities_engagement.py create mode 100644 tests/routers/activities/test_activities_team.py create mode 100644 tests/routers/freelancers/test_freelancers_profile.py create mode 100644 tests/routers/freelancers/test_freelancers_search.py create mode 100644 tests/routers/hr/clients/test_hr_clients_applications.py create mode 100644 tests/routers/hr/clients/test_hr_clients_offers.py create mode 100644 tests/routers/hr/freelancers/test_hr_freelancers_applications.py create mode 100644 tests/routers/hr/freelancers/test_hr_freelancers_offers.py create mode 100644 tests/routers/hr/test_hr_contracts.py create mode 100644 tests/routers/hr/test_hr_engagements.py create mode 100644 tests/routers/hr/test_hr_interviews.py create mode 100644 tests/routers/hr/test_hr_jobs.py create mode 100644 tests/routers/hr/test_hr_milestones.py create mode 100644 tests/routers/hr/test_hr_roles.py create mode 100644 tests/routers/hr/test_hr_submissions.py create mode 100644 tests/routers/jobs/test_jobs_profile.py create mode 100644 tests/routers/jobs/test_jobs_search.py create mode 100644 tests/routers/organization/test_organization_companies.py create mode 100644 tests/routers/organization/test_organization_teams.py create mode 100644 tests/routers/organization/test_organization_users.py create mode 100644 tests/routers/reports/finance/test_reports_finance_accounts.py create mode 100644 tests/routers/reports/finance/test_reports_finance_billings.py create mode 100644 tests/routers/reports/finance/test_reports_finance_earnings.py create mode 100644 tests/routers/reports/test_reports_time.py create mode 100644 tests/routers/test_auth.py create mode 100644 tests/routers/test_messages.py create mode 100644 tests/routers/test_metadata.py create mode 100644 tests/routers/test_payments.py create mode 100644 tests/routers/test_snapshots.py create mode 100644 tests/routers/test_workdays.py create mode 100644 tests/routers/test_workiary.py create mode 100644 tests/test_client.py create mode 100644 tests/test_config.py create mode 100644 tests/test_upwork.py create mode 100644 upwork/__init__.py create mode 100644 upwork/client.py create mode 100644 upwork/config.py create mode 100644 upwork/routers/__init__.py create mode 100644 upwork/routers/activities/__init__.py create mode 100644 upwork/routers/activities/engagement.py create mode 100644 upwork/routers/activities/team.py create mode 100644 upwork/routers/auth.py create mode 100644 upwork/routers/freelancers/__init__.py create mode 100644 upwork/routers/freelancers/profile.py create mode 100644 upwork/routers/freelancers/search.py create mode 100644 upwork/routers/hr/__init__.py create mode 100644 upwork/routers/hr/clients/__init__.py create mode 100644 upwork/routers/hr/clients/applications.py create mode 100644 upwork/routers/hr/clients/offers.py create mode 100644 upwork/routers/hr/contracts.py create mode 100644 upwork/routers/hr/engagements.py create mode 100644 upwork/routers/hr/freelancers/__init__.py create mode 100644 upwork/routers/hr/freelancers/applications.py create mode 100644 upwork/routers/hr/freelancers/offers.py create mode 100644 upwork/routers/hr/interviews.py create mode 100644 upwork/routers/hr/jobs.py create mode 100644 upwork/routers/hr/milestones.py create mode 100644 upwork/routers/hr/roles.py create mode 100644 upwork/routers/hr/submissions.py create mode 100644 upwork/routers/jobs/__init__.py create mode 100644 upwork/routers/jobs/profile.py create mode 100644 upwork/routers/jobs/search.py create mode 100644 upwork/routers/messages.py create mode 100644 upwork/routers/metadata.py create mode 100644 upwork/routers/organization/__init__.py create mode 100644 upwork/routers/organization/companies.py create mode 100644 upwork/routers/organization/teams.py create mode 100644 upwork/routers/organization/users.py create mode 100644 upwork/routers/payments.py create mode 100644 upwork/routers/reports/__init__.py create mode 100644 upwork/routers/reports/finance/__init__.py create mode 100644 upwork/routers/reports/finance/accounts.py create mode 100644 upwork/routers/reports/finance/billings.py create mode 100644 upwork/routers/reports/finance/earnings.py create mode 100644 upwork/routers/reports/time.py create mode 100644 upwork/routers/snapshots.py create mode 100644 upwork/routers/workdays.py create mode 100644 upwork/routers/workdiary.py create mode 100644 upwork/upwork.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..e69de29 diff --git a/.docgen b/.docgen new file mode 100644 index 0000000..3fdb95a --- /dev/null +++ b/.docgen @@ -0,0 +1 @@ +pipenv shell && pdoc3 --html upwork diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..1b4540c --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 88 +ignore = E501, E203, W503, E231 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba208f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,114 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# pipenv +Pipfile* + +# C extensions +*.so + +# nox +.nox/ + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# pdoc3 documentation +html/ + +# mypy +.mypy_cache/ + +# IDE settings +.vscode/ diff --git a/.tests b/.tests new file mode 100644 index 0000000..816817c --- /dev/null +++ b/.tests @@ -0,0 +1 @@ +nox diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c5e0468 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: python +os: linux +cache: + pip: true +matrix: + include: + - python: "3.7" + env: NOXSESSION="tests-3.7" + dist: xenial + - python: "3.8" + env: NOXSESSION="tests-3.8" + - python: "3.7" + env: NOXSESSION="lint" + dist: xenial +install: + - pip install nox + +script: + - nox --non-interactive --session "$NOXSESSION" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b17613f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +# Release History + +## 2.0.0 +* Release of Python 3 compatible library diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7fc598e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,23 @@ +Install project automation tool [`nox`](https://nox.thea.codes/en/stable/): + +``` +python -m pip install --user nox +``` + +### Unit Tests + +``` +nox -s tests +``` + +### Lint + +``` +nox -s lint +``` + +### Publish to PyPI + +``` +nox -s publish +``` \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..be34a98 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include * +include *.md LICENSE + +recursive-include example * + +exclude .flake8 noxfile.py .coverage .coveragerc .gitignore .travis.yml .tests .docgen Pipfile* +prune example +prune tests diff --git a/README.md b/README.md new file mode 100644 index 0000000..c91d528 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +Python bindings for Upwork API +============ + +[![License](http://img.shields.io/packagist/l/upwork/php-upwork.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) +[![PyPI Version](https://badge.fury.io/py/python-upwork.svg)](http://badge.fury.io/py/python-upwork) +[![GitHub release](https://img.shields.io/github/release/upwork/python-upwork.svg)](https://github.com/upwork/python-upwork/releases) +[![Build Status](https://travis-ci.org/upwork/python-upwork.svg)](https://travis-ci.org/upwork/python-upwork) + +# Upwork API + +This project provides a set of resources of Upwork API from http://developers.upwork.com + based on OAuth 1.0a. + +# Features +These are the supported API resources: + +* My Info +* Custom Payments +* Hiring +* Job and Freelancer Profile +* Search Jobs and Freelancers +* Organization +* Messages +* Time and Financial Reporting +* Metadata +* Snapshot +* Team +* Workd Diary +* Activities + +# License + +Copyright 2020 Upwork Corporation. All Rights Reserved. + +python-upwork is licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +## SLA +The usage of this API is ruled by the Terms of Use at: + + https://developers.upwork.com/api-tos.html + +# Requirements +To integrate this library you need to have: + +* Python 3.7+ +* requests_oauthlib >= 1.3.0 + +## Installation + + pip install python-upwork + +All the dependencies will be automatically installed as well. + +## Usage + +1. +Follow instructions from the `Installation` section. + +2. +Run `myapp.py` and follow the instructions, or open `myapp.py` and type the `consumer_key` and `consumer_secret` that you previously got from the API Center. +***That's all. Run your app as `python myapp.py` and have fun.***' diff --git a/example/myapp.py b/example/myapp.py new file mode 100644 index 0000000..cf2021f --- /dev/null +++ b/example/myapp.py @@ -0,0 +1,60 @@ +import upwork +from pprint import pprint +from upwork.routers import auth +#from upwork.routers.jobs import search +#from upwork.routers.activities import team +#from upwork.routers.reports import time +#from urllib.parse import quote + +def get_desktop_client(): + """Emulation of a desktop application. + Your key should be created with the project type "Desktop". + + Returns: ``upwork.Client`` instance ready to work. + + """ + print("Emulating desktop app") + + consumer_key = input('Please enter consumer key: > ') + consumer_secret = input('Please enter key secret: > ') + config = upwork.Config({'consumer_key': consumer_key, 'consumer_secret': consumer_secret}) + """Assign access_token and access_token_secret if they are known + config = upwork.Config({\ + 'consumer_key': 'xxxxxxxxxxx',\ + 'consumer_secret': 'xxxxxxxxxxx',\ + 'access_token': 'xxxxxxxxxxx',\ + 'access_token_secret': 'xxxxxxxxxxx'}) + """ + + client = upwork.Client(config) + + try: + config.access_token + config.access_token_secret + except AttributeError: + verifier = input( + 'Please enter the verification code you get ' + 'following this link:\n{0}\n\n> '.format( + client.get_authorization_url())) + + print('Retrieving keys.... ') + access_token, access_token_secret = client.get_access_token(verifier) + print('OK') + + # For further use you can store ``access_toket`` and + # ``access_token_secret`` somewhere + + return client + +if __name__ == '__main__': + client = get_desktop_client() + + try: + print("My info") + pprint(auth.Api(client).get_user_info()) + #pprint(search.Api(client).find({'q': 'php'})) + #pprint(team.Api(client).add_activity('mycompany', 'mycompany', {'code': 'team-task-001', 'description': 'Description', 'all_in_company': '1'})) + #pprint(time.Gds(client).get_by_freelancer_full('username', {'tq': quote('SELECT task, memo WHERE worked_on >= "2020-05-01" AND worked_on <= "2020-06-01"')})) + except Exception as e: + print("Catch or log exception details") + raise e diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 0000000..417bce2 --- /dev/null +++ b/noxfile.py @@ -0,0 +1,65 @@ +import nox # type: ignore +from pathlib import Path + +nox.options.sessions = ["tests", "lint", "build"] + +python = ["3.8"] + + +lint_dependencies = [ + "-e", + ".", + "black", + "flake8", + "flake8-bugbear", + "mypy", + "check-manifest", +] + + +@nox.session(python=python) +def tests(session): + session.install("-e", ".", "pytest", "pytest-cov") + tests = session.posargs or ["tests"] + session.run( + "pytest", "--cov=upwork", "--cov-config", ".coveragerc", "--cov-report=", *tests + ) + session.notify("cover") + + +@nox.session +def cover(session): + """Coverage analysis""" + session.install("coverage") + session.run("coverage", "report", "--show-missing", "--fail-under=0") + session.run("coverage", "erase") + + +@nox.session(python="3.8") +def lint(session): + session.install(*lint_dependencies) + files = ["tests"] + [str(p) for p in Path(".").glob("*.py")] + session.run("black", "--check", *files) + session.run("flake8", *files) + session.run("mypy", *files) + session.run("python", "setup.py", "check", "--metadata", "--strict") + if "--skip_manifest_check" in session.posargs: + pass + else: + session.run("check-manifest") + + +@nox.session(python="3.8") +def build(session): + session.install("setuptools") + session.install("wheel") + session.install("twine") + session.run("rm", "-rf", "dist", "build", external=True) + session.run("python", "setup.py", "--quiet", "sdist", "bdist_wheel") + + +@nox.session(python="3.8") +def publish(session): + build(session) + print("REMINDER: Has the changelog been updated?") + session.run("python", "-m", "twine", "upload", "dist/*") diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..976b65e --- /dev/null +++ b/setup.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +from setuptools import setup, find_packages # type: ignore + +with open("README.md") as readme_file: + readme = readme_file.read() + +setup( + author="Maksym Novozhylov", + author_email="mnovozhilov@upwork.com", + python_requires=">=3.7", + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Utilities", + ], + description="Python bindings for Upwork API", + install_requires=["requests_oauthlib>=1.3.0"], + license="Apache Software License 2.0", + long_description=readme, + long_description_content_type="text/markdown", + include_package_data=True, + keywords="python-upwork", + name="python-upwork", + packages=find_packages(), + setup_requires=[], + url="https://github.com/upwork/python-upwork", + version="2.0.0", + zip_safe=False, +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..1e361ae --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Unit test package for upwork.""" diff --git a/tests/routers/__init__.py b/tests/routers/__init__.py new file mode 100644 index 0000000..0838208 --- /dev/null +++ b/tests/routers/__init__.py @@ -0,0 +1 @@ +"""Unit test package for upwork routers.""" diff --git a/tests/routers/activities/__init__.py b/tests/routers/activities/__init__.py new file mode 100644 index 0000000..0838208 --- /dev/null +++ b/tests/routers/activities/__init__.py @@ -0,0 +1 @@ +"""Unit test package for upwork routers.""" diff --git a/tests/routers/activities/test_activities_engagement.py b/tests/routers/activities/test_activities_engagement.py new file mode 100644 index 0000000..6603051 --- /dev/null +++ b/tests/routers/activities/test_activities_engagement.py @@ -0,0 +1,24 @@ +import upwork +from upwork.routers.activities import engagement +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_specific(mocked_method): + engagement.Api(upwork.Client).get_specific("1234") + mocked_method.assert_called_with("/tasks/v2/tasks/contracts/1234") + + +@patch.object(upwork.Client, "put") +def test_assign(mocked_method): + engagement.Api(upwork.Client).assign("company", "team", "1234", {"a": "b"}) + mocked_method.assert_called_with( + "/otask/v1/tasks/companies/company/teams/team/engagements/1234/tasks", + {"a": "b"}, + ) + + +@patch.object(upwork.Client, "put") +def test_assign_to_engagement(mocked_method): + engagement.Api(upwork.Client).assign_to_engagement("1234", {"a": "b"}) + mocked_method.assert_called_with("/tasks/v2/tasks/contracts/1234", {"a": "b"}) diff --git a/tests/routers/activities/test_activities_team.py b/tests/routers/activities/test_activities_team.py new file mode 100644 index 0000000..43654d1 --- /dev/null +++ b/tests/routers/activities/test_activities_team.py @@ -0,0 +1,59 @@ +import upwork +from upwork.routers.activities import team +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_list(mocked_method): + team.Api(upwork.Client).get_list("company", "team") + mocked_method.assert_called_with( + "/otask/v1/tasks/companies/company/teams/team/tasks" + ) + + +@patch.object(upwork.Client, "get") +def test_get_specific_list(mocked_method): + team.Api(upwork.Client).get_specific_list("company", "team", "code") + mocked_method.assert_called_with( + "/otask/v1/tasks/companies/company/teams/team/tasks/code" + ) + + +@patch.object(upwork.Client, "post") +def test_add_activity(mocked_method): + team.Api(upwork.Client).add_activity("company", "team", {"a": "b"}) + mocked_method.assert_called_with( + "/otask/v1/tasks/companies/company/teams/team/tasks", {"a": "b"} + ) + + +@patch.object(upwork.Client, "put") +def test_update_activities(mocked_method): + team.Api(upwork.Client).update_activities("company", "team", "code", {"a": "b"}) + mocked_method.assert_called_with( + "/otask/v1/tasks/companies/company/teams/team/tasks/code", {"a": "b"} + ) + + +@patch.object(upwork.Client, "put") +def test_archive_activities(mocked_method): + team.Api(upwork.Client).archive_activities("company", "team", "code") + mocked_method.assert_called_with( + "/otask/v1/tasks/companies/company/teams/team/archive/code" + ) + + +@patch.object(upwork.Client, "put") +def test_unarchive_activities(mocked_method): + team.Api(upwork.Client).unarchive_activities("company", "team", "code") + mocked_method.assert_called_with( + "/otask/v1/tasks/companies/company/teams/team/unarchive/code" + ) + + +@patch.object(upwork.Client, "put") +def test_update_batch(mocked_method): + team.Api(upwork.Client).update_batch("company", {"a": "b"}) + mocked_method.assert_called_with( + "/otask/v1/tasks/companies/company/tasks/batch", {"a": "b"} + ) diff --git a/tests/routers/freelancers/test_freelancers_profile.py b/tests/routers/freelancers/test_freelancers_profile.py new file mode 100644 index 0000000..75d86a3 --- /dev/null +++ b/tests/routers/freelancers/test_freelancers_profile.py @@ -0,0 +1,15 @@ +import upwork +from upwork.routers.freelancers import profile +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_specific(mocked_method): + profile.Api(upwork.Client).get_specific("key") + mocked_method.assert_called_with("/profiles/v1/providers/key") + + +@patch.object(upwork.Client, "get") +def test_get_specific_brief(mocked_method): + profile.Api(upwork.Client).get_specific_brief("key") + mocked_method.assert_called_with("/profiles/v1/providers/key/brief") diff --git a/tests/routers/freelancers/test_freelancers_search.py b/tests/routers/freelancers/test_freelancers_search.py new file mode 100644 index 0000000..6c12e58 --- /dev/null +++ b/tests/routers/freelancers/test_freelancers_search.py @@ -0,0 +1,9 @@ +import upwork +from upwork.routers.freelancers import search +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_find(mocked_method): + search.Api(upwork.Client).find({"a": "b"}) + mocked_method.assert_called_with("/profiles/v2/search/providers", {"a": "b"}) diff --git a/tests/routers/hr/clients/test_hr_clients_applications.py b/tests/routers/hr/clients/test_hr_clients_applications.py new file mode 100644 index 0000000..d6e59a1 --- /dev/null +++ b/tests/routers/hr/clients/test_hr_clients_applications.py @@ -0,0 +1,17 @@ +import upwork +from upwork.routers.hr.clients import applications +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_list(mocked_method): + applications.Api(upwork.Client).get_list({"a": "b"}) + mocked_method.assert_called_with("/hr/v4/clients/applications", {"a": "b"}) + + +@patch.object(upwork.Client, "get") +def test_get_specific(mocked_method): + applications.Api(upwork.Client).get_specific("reference", {"a": "b"}) + mocked_method.assert_called_with( + "/hr/v4/clients/applications/reference", {"a": "b"} + ) diff --git a/tests/routers/hr/clients/test_hr_clients_offers.py b/tests/routers/hr/clients/test_hr_clients_offers.py new file mode 100644 index 0000000..f66542e --- /dev/null +++ b/tests/routers/hr/clients/test_hr_clients_offers.py @@ -0,0 +1,21 @@ +import upwork +from upwork.routers.hr.clients import offers +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_list(mocked_method): + offers.Api(upwork.Client).get_list({"a": "b"}) + mocked_method.assert_called_with("/offers/v1/clients/offers", {"a": "b"}) + + +@patch.object(upwork.Client, "get") +def test_get_specific(mocked_method): + offers.Api(upwork.Client).get_specific("reference", {"a": "b"}) + mocked_method.assert_called_with("/offers/v1/clients/offers/reference", {"a": "b"}) + + +@patch.object(upwork.Client, "post") +def test_make_offer(mocked_method): + offers.Api(upwork.Client).make_offer({"a": "b"}) + mocked_method.assert_called_with("/offers/v1/clients/offers", {"a": "b"}) diff --git a/tests/routers/hr/freelancers/test_hr_freelancers_applications.py b/tests/routers/hr/freelancers/test_hr_freelancers_applications.py new file mode 100644 index 0000000..1fd591a --- /dev/null +++ b/tests/routers/hr/freelancers/test_hr_freelancers_applications.py @@ -0,0 +1,15 @@ +import upwork +from upwork.routers.hr.freelancers import applications +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_list(mocked_method): + applications.Api(upwork.Client).get_list({}) + mocked_method.assert_called_with("/hr/v4/contractors/applications", {}) + + +@patch.object(upwork.Client, "get") +def test_get_specific(mocked_method): + applications.Api(upwork.Client).get_specific("reference") + mocked_method.assert_called_with("/hr/v4/contractors/applications/reference") diff --git a/tests/routers/hr/freelancers/test_hr_freelancers_offers.py b/tests/routers/hr/freelancers/test_hr_freelancers_offers.py new file mode 100644 index 0000000..4b8c85d --- /dev/null +++ b/tests/routers/hr/freelancers/test_hr_freelancers_offers.py @@ -0,0 +1,23 @@ +import upwork +from upwork.routers.hr.freelancers import offers +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_list(mocked_method): + offers.Api(upwork.Client).get_list({}) + mocked_method.assert_called_with("/offers/v1/contractors/offers", {}) + + +@patch.object(upwork.Client, "get") +def test_get_specific(mocked_method): + offers.Api(upwork.Client).get_specific("reference") + mocked_method.assert_called_with("/offers/v1/contractors/offers/reference") + + +@patch.object(upwork.Client, "post") +def test_actions(mocked_method): + offers.Api(upwork.Client).actions("reference", {"a": "b"}) + mocked_method.assert_called_with( + "/offers/v1/contractors/actions/reference", {"a": "b"} + ) diff --git a/tests/routers/hr/test_hr_contracts.py b/tests/routers/hr/test_hr_contracts.py new file mode 100644 index 0000000..f82b3b3 --- /dev/null +++ b/tests/routers/hr/test_hr_contracts.py @@ -0,0 +1,21 @@ +import upwork +from upwork.routers.hr import contracts +from unittest.mock import patch + + +@patch.object(upwork.Client, "put") +def test_suspend_contract(mocked_method): + contracts.Api(upwork.Client).suspend_contract("reference", {"a": "b"}) + mocked_method.assert_called_with("/hr/v2/contracts/reference/suspend", {"a": "b"}) + + +@patch.object(upwork.Client, "put") +def test_restart_contract(mocked_method): + contracts.Api(upwork.Client).restart_contract("reference", {"a": "b"}) + mocked_method.assert_called_with("/hr/v2/contracts/reference/restart", {"a": "b"}) + + +@patch.object(upwork.Client, "delete") +def test_end_contract(mocked_method): + contracts.Api(upwork.Client).end_contract("reference", {"a": "b"}) + mocked_method.assert_called_with("/hr/v2/contracts/reference", {"a": "b"}) diff --git a/tests/routers/hr/test_hr_engagements.py b/tests/routers/hr/test_hr_engagements.py new file mode 100644 index 0000000..75c4b26 --- /dev/null +++ b/tests/routers/hr/test_hr_engagements.py @@ -0,0 +1,15 @@ +import upwork +from upwork.routers.hr import engagements +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_list(mocked_method): + engagements.Api(upwork.Client).get_list({"a": "b"}) + mocked_method.assert_called_with("/hr/v2/engagements", {"a": "b"}) + + +@patch.object(upwork.Client, "get") +def test_get_specific(mocked_method): + engagements.Api(upwork.Client).get_specific("reference") + mocked_method.assert_called_with("/hr/v2/engagements/reference") diff --git a/tests/routers/hr/test_hr_interviews.py b/tests/routers/hr/test_hr_interviews.py new file mode 100644 index 0000000..9971853 --- /dev/null +++ b/tests/routers/hr/test_hr_interviews.py @@ -0,0 +1,9 @@ +import upwork +from upwork.routers.hr import interviews +from unittest.mock import patch + + +@patch.object(upwork.Client, "post") +def test_invite(mocked_method): + interviews.Api(upwork.Client).invite("key", {"a": "b"}) + mocked_method.assert_called_with("/hr/v1/jobs/key/candidates", {"a": "b"}) diff --git a/tests/routers/hr/test_hr_jobs.py b/tests/routers/hr/test_hr_jobs.py new file mode 100644 index 0000000..f67134e --- /dev/null +++ b/tests/routers/hr/test_hr_jobs.py @@ -0,0 +1,33 @@ +import upwork +from upwork.routers.hr import jobs +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_list(mocked_method): + jobs.Api(upwork.Client).get_list({"a": "b"}) + mocked_method.assert_called_with("/hr/v2/jobs", {"a": "b"}) + + +@patch.object(upwork.Client, "get") +def test_get_specific(mocked_method): + jobs.Api(upwork.Client).get_specific("key") + mocked_method.assert_called_with("/hr/v2/jobs/key") + + +@patch.object(upwork.Client, "post") +def test_post_job(mocked_method): + jobs.Api(upwork.Client).post_job({"a": "b"}) + mocked_method.assert_called_with("/hr/v2/jobs", {"a": "b"}) + + +@patch.object(upwork.Client, "put") +def test_edit_job(mocked_method): + jobs.Api(upwork.Client).edit_job("key", {"a": "b"}) + mocked_method.assert_called_with("/hr/v2/jobs/key", {"a": "b"}) + + +@patch.object(upwork.Client, "delete") +def test_delete_job(mocked_method): + jobs.Api(upwork.Client).delete_job("key", {"a": "b"}) + mocked_method.assert_called_with("/hr/v2/jobs/key", {"a": "b"}) diff --git a/tests/routers/hr/test_hr_milestones.py b/tests/routers/hr/test_hr_milestones.py new file mode 100644 index 0000000..063a9bd --- /dev/null +++ b/tests/routers/hr/test_hr_milestones.py @@ -0,0 +1,51 @@ +import upwork +from upwork.routers.hr import milestones +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_active_milestone(mocked_method): + milestones.Api(upwork.Client).get_active_milestone("contract") + mocked_method.assert_called_with( + "/hr/v3/fp/milestones/statuses/active/contracts/contract" + ) + + +@patch.object(upwork.Client, "get") +def test_get_submissions(mocked_method): + milestones.Api(upwork.Client).get_submissions("milestone") + mocked_method.assert_called_with("/hr/v3/fp/milestones/milestone/submissions") + + +@patch.object(upwork.Client, "post") +def test_create(mocked_method): + milestones.Api(upwork.Client).create({"a": "b"}) + mocked_method.assert_called_with("/hr/v3/fp/milestones", {"a": "b"}) + + +@patch.object(upwork.Client, "put") +def test_edit(mocked_method): + milestones.Api(upwork.Client).edit("milestone", {"a": "b"}) + mocked_method.assert_called_with("/hr/v3/fp/milestones/milestone", {"a": "b"}) + + +@patch.object(upwork.Client, "put") +def test_activate(mocked_method): + milestones.Api(upwork.Client).activate("milestone", {"a": "b"}) + mocked_method.assert_called_with( + "/hr/v3/fp/milestones/milestone/activate", {"a": "b"} + ) + + +@patch.object(upwork.Client, "put") +def test_approve(mocked_method): + milestones.Api(upwork.Client).approve("milestone", {"a": "b"}) + mocked_method.assert_called_with( + "/hr/v3/fp/milestones/milestone/approve", {"a": "b"} + ) + + +@patch.object(upwork.Client, "delete") +def test_delete(mocked_method): + milestones.Api(upwork.Client).delete("milestone") + mocked_method.assert_called_with("/hr/v3/fp/milestones/milestone") diff --git a/tests/routers/hr/test_hr_roles.py b/tests/routers/hr/test_hr_roles.py new file mode 100644 index 0000000..64bd177 --- /dev/null +++ b/tests/routers/hr/test_hr_roles.py @@ -0,0 +1,15 @@ +import upwork +from upwork.routers.hr import roles +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_all(mocked_method): + roles.Api(upwork.Client).get_all() + mocked_method.assert_called_with("/hr/v2/userroles") + + +@patch.object(upwork.Client, "get") +def test_get_by_specific_user(mocked_method): + roles.Api(upwork.Client).get_by_specific_user("reference") + mocked_method.assert_called_with("/hr/v2/userroles/reference") diff --git a/tests/routers/hr/test_hr_submissions.py b/tests/routers/hr/test_hr_submissions.py new file mode 100644 index 0000000..190a217 --- /dev/null +++ b/tests/routers/hr/test_hr_submissions.py @@ -0,0 +1,25 @@ +import upwork +from upwork.routers.hr import submissions +from unittest.mock import patch + + +@patch.object(upwork.Client, "post") +def test_request_approval(mocked_method): + submissions.Api(upwork.Client).request_approval({"a": "b"}) + mocked_method.assert_called_with("/hr/v3/fp/submissions", {"a": "b"}) + + +@patch.object(upwork.Client, "put") +def test_approve(mocked_method): + submissions.Api(upwork.Client).approve("submission", {"a": "b"}) + mocked_method.assert_called_with( + "/hr/v3/fp/submissions/submission/approve", {"a": "b"} + ) + + +@patch.object(upwork.Client, "put") +def test_reject(mocked_method): + submissions.Api(upwork.Client).reject("submission", {"a": "b"}) + mocked_method.assert_called_with( + "/hr/v3/fp/submissions/submission/reject", {"a": "b"} + ) diff --git a/tests/routers/jobs/test_jobs_profile.py b/tests/routers/jobs/test_jobs_profile.py new file mode 100644 index 0000000..12a0500 --- /dev/null +++ b/tests/routers/jobs/test_jobs_profile.py @@ -0,0 +1,9 @@ +import upwork +from upwork.routers.jobs import profile +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_specific(mocked_method): + profile.Api(upwork.Client).get_specific("key") + mocked_method.assert_called_with("/profiles/v1/jobs/key") diff --git a/tests/routers/jobs/test_jobs_search.py b/tests/routers/jobs/test_jobs_search.py new file mode 100644 index 0000000..222a66b --- /dev/null +++ b/tests/routers/jobs/test_jobs_search.py @@ -0,0 +1,9 @@ +import upwork +from upwork.routers.jobs import search +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_find(mocked_method): + search.Api(upwork.Client).find({"a": "b"}) + mocked_method.assert_called_with("/profiles/v2/search/jobs", {"a": "b"}) diff --git a/tests/routers/organization/test_organization_companies.py b/tests/routers/organization/test_organization_companies.py new file mode 100644 index 0000000..65685a3 --- /dev/null +++ b/tests/routers/organization/test_organization_companies.py @@ -0,0 +1,27 @@ +import upwork +from upwork.routers.organization import companies +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_list(mocked_method): + companies.Api(upwork.Client).get_list() + mocked_method.assert_called_with("/hr/v2/companies") + + +@patch.object(upwork.Client, "get") +def test_get_specific(mocked_method): + companies.Api(upwork.Client).get_specific("reference") + mocked_method.assert_called_with("/hr/v2/companies/reference") + + +@patch.object(upwork.Client, "get") +def test_get_teams(mocked_method): + companies.Api(upwork.Client).get_teams("reference") + mocked_method.assert_called_with("/hr/v2/companies/reference/teams") + + +@patch.object(upwork.Client, "get") +def test_users(mocked_method): + companies.Api(upwork.Client).get_users("reference") + mocked_method.assert_called_with("/hr/v2/companies/reference/users") diff --git a/tests/routers/organization/test_organization_teams.py b/tests/routers/organization/test_organization_teams.py new file mode 100644 index 0000000..172aa3a --- /dev/null +++ b/tests/routers/organization/test_organization_teams.py @@ -0,0 +1,15 @@ +import upwork +from upwork.routers.organization import teams +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_list(mocked_method): + teams.Api(upwork.Client).get_list() + mocked_method.assert_called_with("/hr/v2/teams") + + +@patch.object(upwork.Client, "get") +def test_get_users_in_team(mocked_method): + teams.Api(upwork.Client).get_users_in_team("reference") + mocked_method.assert_called_with("/hr/v2/teams/reference/users") diff --git a/tests/routers/organization/test_organization_users.py b/tests/routers/organization/test_organization_users.py new file mode 100644 index 0000000..03a5dac --- /dev/null +++ b/tests/routers/organization/test_organization_users.py @@ -0,0 +1,15 @@ +import upwork +from upwork.routers.organization import users +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_my_info(mocked_method): + users.Api(upwork.Client).get_my_info() + mocked_method.assert_called_with("/hr/v2/users/me") + + +@patch.object(upwork.Client, "get") +def test_get_specific(mocked_method): + users.Api(upwork.Client).get_specific("reference") + mocked_method.assert_called_with("/hr/v2/users/reference") diff --git a/tests/routers/reports/finance/test_reports_finance_accounts.py b/tests/routers/reports/finance/test_reports_finance_accounts.py new file mode 100644 index 0000000..4ee4f9c --- /dev/null +++ b/tests/routers/reports/finance/test_reports_finance_accounts.py @@ -0,0 +1,23 @@ +import upwork +from upwork.routers.reports.finance import accounts +from unittest.mock import patch + + +def test_entry_point(): + assert accounts.Gds.entry_point == "gds" + + +@patch.object(upwork.Client, "get") +def test_get_owned(mocked_method): + accounts.Gds(upwork.Client).get_owned("freelancer", {"a": "b"}) + mocked_method.assert_called_with( + "/finreports/v2/financial_account_owner/freelancer", {"a": "b"} + ) + + +@patch.object(upwork.Client, "get") +def test_get_specific(mocked_method): + accounts.Gds(upwork.Client).get_specific("entity", {"a": "b"}) + mocked_method.assert_called_with( + "/finreports/v2/financial_accounts/entity", {"a": "b"} + ) diff --git a/tests/routers/reports/finance/test_reports_finance_billings.py b/tests/routers/reports/finance/test_reports_finance_billings.py new file mode 100644 index 0000000..e01ea34 --- /dev/null +++ b/tests/routers/reports/finance/test_reports_finance_billings.py @@ -0,0 +1,47 @@ +import upwork +from upwork.routers.reports.finance import billings +from unittest.mock import patch + + +def test_entry_point(): + assert billings.Gds.entry_point == "gds" + + +@patch.object(upwork.Client, "get") +def test_get_by_freelancer(mocked_method): + billings.Gds(upwork.Client).get_by_freelancer("freelancer", {"a": "b"}) + mocked_method.assert_called_with( + "/finreports/v2/providers/freelancer/billings", {"a": "b"} + ) + + +@patch.object(upwork.Client, "get") +def test_get_by_freelancers_team(mocked_method): + billings.Gds(upwork.Client).get_by_freelancers_team("team", {"a": "b"}) + mocked_method.assert_called_with( + "/finreports/v2/provider_teams/team/billings", {"a": "b"} + ) + + +@patch.object(upwork.Client, "get") +def test_get_by_freelancers_company(mocked_method): + billings.Gds(upwork.Client).get_by_freelancers_company("company", {"a": "b"}) + mocked_method.assert_called_with( + "/finreports/v2/provider_companies/company/billings", {"a": "b"} + ) + + +@patch.object(upwork.Client, "get") +def test_get_by_buyers_team(mocked_method): + billings.Gds(upwork.Client).get_by_buyers_team("team", {"a": "b"}) + mocked_method.assert_called_with( + "/finreports/v2/buyer_teams/team/billings", {"a": "b"} + ) + + +@patch.object(upwork.Client, "get") +def test_get_by_buyers_company(mocked_method): + billings.Gds(upwork.Client).get_by_buyers_company("company", {"a": "b"}) + mocked_method.assert_called_with( + "/finreports/v2/buyer_companies/company/billings", {"a": "b"} + ) diff --git a/tests/routers/reports/finance/test_reports_finance_earnings.py b/tests/routers/reports/finance/test_reports_finance_earnings.py new file mode 100644 index 0000000..a20b104 --- /dev/null +++ b/tests/routers/reports/finance/test_reports_finance_earnings.py @@ -0,0 +1,47 @@ +import upwork +from upwork.routers.reports.finance import earnings +from unittest.mock import patch + + +def test_entry_point(): + assert earnings.Gds.entry_point == "gds" + + +@patch.object(upwork.Client, "get") +def test_get_by_freelancer(mocked_method): + earnings.Gds(upwork.Client).get_by_freelancer("freelancer", {"a": "b"}) + mocked_method.assert_called_with( + "/finreports/v2/providers/freelancer/earnings", {"a": "b"} + ) + + +@patch.object(upwork.Client, "get") +def test_get_by_freelancers_team(mocked_method): + earnings.Gds(upwork.Client).get_by_freelancers_team("team", {"a": "b"}) + mocked_method.assert_called_with( + "/finreports/v2/provider_teams/team/earnings", {"a": "b"} + ) + + +@patch.object(upwork.Client, "get") +def test_get_by_freelancers_company(mocked_method): + earnings.Gds(upwork.Client).get_by_freelancers_company("company", {"a": "b"}) + mocked_method.assert_called_with( + "/finreports/v2/provider_companies/company/earnings", {"a": "b"} + ) + + +@patch.object(upwork.Client, "get") +def test_get_by_buyers_team(mocked_method): + earnings.Gds(upwork.Client).get_by_buyers_team("team", {"a": "b"}) + mocked_method.assert_called_with( + "/finreports/v2/buyer_teams/team/earnings", {"a": "b"} + ) + + +@patch.object(upwork.Client, "get") +def test_get_by_buyers_company(mocked_method): + earnings.Gds(upwork.Client).get_by_buyers_company("company", {"a": "b"}) + mocked_method.assert_called_with( + "/finreports/v2/buyer_companies/company/earnings", {"a": "b"} + ) diff --git a/tests/routers/reports/test_reports_time.py b/tests/routers/reports/test_reports_time.py new file mode 100644 index 0000000..6bef007 --- /dev/null +++ b/tests/routers/reports/test_reports_time.py @@ -0,0 +1,51 @@ +import upwork +from upwork.routers.reports import time +from unittest.mock import patch + + +def test_entry_point(): + assert time.Gds.entry_point == "gds" + + +@patch.object(upwork.Client, "get") +def test_get_by_team_full(mocked_method): + time.Gds(upwork.Client).get_by_team_full("company", "team", {"a": "b"}) + mocked_method.assert_called_with( + "/timereports/v1/companies/company/teams/team", {"a": "b"} + ) + + +@patch.object(upwork.Client, "get") +def test_get_by_team_limited(mocked_method): + time.Gds(upwork.Client).get_by_team_limited("company", "team", {"a": "b"}) + mocked_method.assert_called_with( + "/timereports/v1/companies/company/teams/team/hours", {"a": "b"} + ) + + +@patch.object(upwork.Client, "get") +def test_get_by_agency(mocked_method): + time.Gds(upwork.Client).get_by_agency("company", "agency", {"a": "b"}) + mocked_method.assert_called_with( + "/timereports/v1/companies/company/agencies/agency", {"a": "b"} + ) + + +@patch.object(upwork.Client, "get") +def test_get_by_company(mocked_method): + time.Gds(upwork.Client).get_by_company("company", {"a": "b"}) + mocked_method.assert_called_with("/timereports/v1/companies/company", {"a": "b"}) + + +@patch.object(upwork.Client, "get") +def test_get_by_freelancer_limited(mocked_method): + time.Gds(upwork.Client).get_by_freelancer_limited("freelancer", {"a": "b"}) + mocked_method.assert_called_with( + "/timereports/v1/providers/freelancer/hours", {"a": "b"} + ) + + +@patch.object(upwork.Client, "get") +def test_get_by_freelancer_full(mocked_method): + time.Gds(upwork.Client).get_by_freelancer_full("freelancer", {"a": "b"}) + mocked_method.assert_called_with("/timereports/v1/providers/freelancer", {"a": "b"}) diff --git a/tests/routers/test_auth.py b/tests/routers/test_auth.py new file mode 100644 index 0000000..d1d70b4 --- /dev/null +++ b/tests/routers/test_auth.py @@ -0,0 +1,9 @@ +import upwork +from upwork.routers import auth +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_user_info(mocked_method): + auth.Api(upwork.Client).get_user_info() + mocked_method.assert_called_with("/auth/v1/info") diff --git a/tests/routers/test_messages.py b/tests/routers/test_messages.py new file mode 100644 index 0000000..4532915 --- /dev/null +++ b/tests/routers/test_messages.py @@ -0,0 +1,69 @@ +import upwork +from upwork.routers import messages +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_rooms(mocked_method): + messages.Api(upwork.Client).get_rooms("company") + mocked_method.assert_called_with("/messages/v3/company/rooms", {}) + + +@patch.object(upwork.Client, "get") +def test_get_room_details(mocked_method): + messages.Api(upwork.Client).get_room_details("company", "room_id") + mocked_method.assert_called_with("/messages/v3/company/rooms/room_id", {}) + + +@patch.object(upwork.Client, "get") +def test_get_room_messages(mocked_method): + messages.Api(upwork.Client).get_room_messages("company", "room_id") + mocked_method.assert_called_with("/messages/v3/company/rooms/room_id/stories", {}) + + +@patch.object(upwork.Client, "get") +def test_get_room_by_offer(mocked_method): + messages.Api(upwork.Client).get_room_by_offer("company", "offer_id") + mocked_method.assert_called_with("/messages/v3/company/rooms/offers/offer_id", {}) + + +@patch.object(upwork.Client, "get") +def test_get_room_by_application(mocked_method): + messages.Api(upwork.Client).get_room_by_application("company", "application_id") + mocked_method.assert_called_with( + "/messages/v3/company/rooms/applications/application_id", {} + ) + + +@patch.object(upwork.Client, "get") +def test_get_room_by_contract(mocked_method): + messages.Api(upwork.Client).get_room_by_contract("company", "contract_id") + mocked_method.assert_called_with( + "/messages/v3/company/rooms/contracts/contract_id", {} + ) + + +@patch.object(upwork.Client, "post") +def test_create_room(mocked_method): + messages.Api(upwork.Client).create_room("company") + mocked_method.assert_called_with("/messages/v3/company/rooms", {}) + + +@patch.object(upwork.Client, "post") +def test_send_message_to_room(mocked_method): + messages.Api(upwork.Client).send_message_to_room("company", "room_id") + mocked_method.assert_called_with("/messages/v3/company/rooms/room_id/stories", {}) + + +@patch.object(upwork.Client, "put") +def test_get_update_room_settings(mocked_method): + messages.Api(upwork.Client).update_room_settings("company", "room_id", "username") + mocked_method.assert_called_with( + "/messages/v3/company/rooms/room_id/users/username", {} + ) + + +@patch.object(upwork.Client, "put") +def test_get_update_room_metadata(mocked_method): + messages.Api(upwork.Client).update_room_metadata("company", "room_id") + mocked_method.assert_called_with("/messages/v3/company/rooms/room_id", {}) diff --git a/tests/routers/test_metadata.py b/tests/routers/test_metadata.py new file mode 100644 index 0000000..6bdad42 --- /dev/null +++ b/tests/routers/test_metadata.py @@ -0,0 +1,45 @@ +import upwork +from upwork.routers import metadata +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_categories_v2(mocked_method): + metadata.Api(upwork.Client).get_categories_v2() + mocked_method.assert_called_with("/profiles/v2/metadata/categories") + + +@patch.object(upwork.Client, "get") +def test_get_skills(mocked_method): + metadata.Api(upwork.Client).get_skills() + mocked_method.assert_called_with("/profiles/v1/metadata/skills") + + +@patch.object(upwork.Client, "get") +def test_get_skills_v2(mocked_method): + metadata.Api(upwork.Client).get_skills_v2({"a": "b"}) + mocked_method.assert_called_with("/profiles/v2/metadata/skills", {"a": "b"}) + + +@patch.object(upwork.Client, "get") +def test_get_specialties(mocked_method): + metadata.Api(upwork.Client).get_specialties() + mocked_method.assert_called_with("/profiles/v1/metadata/specialties") + + +@patch.object(upwork.Client, "get") +def test_get_regions(mocked_method): + metadata.Api(upwork.Client).get_regions() + mocked_method.assert_called_with("/profiles/v1/metadata/regions") + + +@patch.object(upwork.Client, "get") +def test_get_tests(mocked_method): + metadata.Api(upwork.Client).get_tests() + mocked_method.assert_called_with("/profiles/v1/metadata/tests") + + +@patch.object(upwork.Client, "get") +def test_get_reasons(mocked_method): + metadata.Api(upwork.Client).get_reasons({"a": "b"}) + mocked_method.assert_called_with("/profiles/v1/metadata/reasons", {"a": "b"}) diff --git a/tests/routers/test_payments.py b/tests/routers/test_payments.py new file mode 100644 index 0000000..37ceee2 --- /dev/null +++ b/tests/routers/test_payments.py @@ -0,0 +1,9 @@ +import upwork +from upwork.routers import payments +from unittest.mock import patch + + +@patch.object(upwork.Client, "post") +def test_submit_bonus(mocked_method): + payments.Api(upwork.Client).submit_bonus(1234, {"a": "b"}) + mocked_method.assert_called_with("/hr/v2/teams/1234/adjustments", {"a": "b"}) diff --git a/tests/routers/test_snapshots.py b/tests/routers/test_snapshots.py new file mode 100644 index 0000000..32c99db --- /dev/null +++ b/tests/routers/test_snapshots.py @@ -0,0 +1,23 @@ +import upwork +from upwork.routers import snapshots +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_by_contract(mocked_method): + snapshots.Api(upwork.Client).get_by_contract("1234", "1234") + mocked_method.assert_called_with("/team/v3/snapshots/contracts/1234/1234") + + +@patch.object(upwork.Client, "put") +def test_update_by_contract(mocked_method): + snapshots.Api(upwork.Client).update_by_contract("1234", "1234", {"a": "b"}) + mocked_method.assert_called_with( + "/team/v3/snapshots/contracts/1234/1234", {"a": "b"} + ) + + +@patch.object(upwork.Client, "delete") +def test_delete_by_contract(mocked_method): + snapshots.Api(upwork.Client).delete_by_contract("1234", "1234") + mocked_method.assert_called_with("/team/v3/snapshots/contracts/1234/1234") diff --git a/tests/routers/test_workdays.py b/tests/routers/test_workdays.py new file mode 100644 index 0000000..dfa1975 --- /dev/null +++ b/tests/routers/test_workdays.py @@ -0,0 +1,19 @@ +import upwork +from upwork.routers import workdays +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_by_company(mocked_method): + workdays.Api(upwork.Client).get_by_company("company", "from", "till", {}) + mocked_method.assert_called_with( + "/team/v3/workdays/companies/company/from,till", {} + ) + + +@patch.object(upwork.Client, "get") +def test_get_by_contract(mocked_method): + workdays.Api(upwork.Client).get_by_contract("company", "from", "till", {}) + mocked_method.assert_called_with( + "/team/v3/workdays/contracts/company/from,till", {} + ) diff --git a/tests/routers/test_workiary.py b/tests/routers/test_workiary.py new file mode 100644 index 0000000..16942b4 --- /dev/null +++ b/tests/routers/test_workiary.py @@ -0,0 +1,15 @@ +import upwork +from upwork.routers import workdiary +from unittest.mock import patch + + +@patch.object(upwork.Client, "get") +def test_get_workdiary(mocked_method): + workdiary.Api(upwork.Client).get_workdiary("company", "date", {}) + mocked_method.assert_called_with("/team/v3/workdiaries/companies/company/date", {}) + + +@patch.object(upwork.Client, "get") +def test_get_by_contract(mocked_method): + workdiary.Api(upwork.Client).get_by_contract("company", "date", {}) + mocked_method.assert_called_with("/team/v3/workdiaries/contracts/company/date", {}) diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 0000000..c7a8e1d --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,135 @@ +import requests +import unittest +from upwork import config +from upwork import client +from unittest.mock import patch, Mock + + +class TestClient(unittest.TestCase): + @patch.object( + requests, + "post", + return_value=Mock( + status_code=200, content=b"oauth_token=token&oauth_token_secret=secret" + ), + ) + def test_get_request_token(self, mocked_post): + cfg = config.Config( + { + "consumer_key": "keyxxxxxxxxxxxxxxxxxxxx", + "consumer_secret": "secretxxxxxxxxxx", + } + ) + cl = client.Client(cfg) + cl.get_request_token() + + assert cl.request_token == "token" + assert cl.request_token_secret == "secret" + + @patch.object(requests, "post", side_effect=Exception("error")) + def test_get_request_token_failed_post(self, mocked_post): + cfg = config.Config( + { + "consumer_key": "keyxxxxxxxxxxxxxxxxxxxx", + "consumer_secret": "secretxxxxxxxxxx", + } + ) + cl = client.Client(cfg) + with self.assertRaises(Exception): + cl.get_request_token() + + @patch.object( + requests, + "post", + return_value=Mock( + status_code=200, content=b"oauth_token=token&oauth_token_secret=secret" + ), + ) + def test_get_access_token(self, mocked_post): + cfg = config.Config( + { + "consumer_key": "keyxxxxxxxxxxxxxxxxxxxx", + "consumer_secret": "secretxxxxxxxxxx", + } + ) + cl = client.Client(cfg) + cl.request_token = "request_token" + cl.request_token_secret = "request_secret" + cl.get_access_token("verifier") + + assert cl.config.access_token == "token" + assert cl.config.access_token_secret == "secret" + + def test_get_access_token_not_ready(self): + cl = client.Client(object) + + with self.assertRaises(Exception): + cl.get_access_token("verifier") + + @patch.object(requests, "post", side_effect=Exception("error")) + def test_get_access_token_failed_post(self, mocked_post): + cfg = config.Config( + { + "consumer_key": "keyxxxxxxxxxxxxxxxxxxxx", + "consumer_secret": "secretxxxxxxxxxx", + } + ) + cl = client.Client(cfg) + cl.request_token = "request_token" + cl.request_token_secret = "request_secret" + + with self.assertRaises(Exception): + cl.get_access_token("verifier") + + @patch.object( + requests, "get", return_value=Mock(status_code=200, json=lambda: {"a": "b"}) + ) + @patch.object( + requests, "post", return_value=Mock(status_code=200, json=lambda: {"a": "b"}) + ) + @patch.object( + requests, "put", return_value=Mock(status_code=200, json=lambda: {"a": "b"}) + ) + @patch.object( + requests, "delete", return_value=Mock(status_code=200, json=lambda: {"a": "b"}) + ) + def test_send_request(self, mocked_get, mocked_post, mocked_put, mocked_delete): + cfg = config.Config( + { + "consumer_key": "keyxxxxxxxxxxxxxxxxxxxx", + "consumer_secret": "secretxxxxxxxxxx", + "access_token": "tokenxxxxxxxxxxxxxxxxxxxx", + "access_token_secret": "tokensecretxxxxx", + } + ) + cl = client.Client(cfg) + assert cl.get("/test/uri", {}) == {"a": "b"} + assert cl.post("/test/uri", {}) == {"a": "b"} + assert cl.put("/test/uri", {}) == {"a": "b"} + assert cl.delete("/test/uri", {}) == {"a": "b"} + + with self.assertRaises(ValueError): + cl.send_request("/test/uri", "method", {}) + + def test_get_authorization_url(self): + cl = client.Client(object) + cl.request_token = "token" + + assert ( + cl.get_authorization_url("https://callback") + == "https://www.upwork.com/services/api/auth?oauth_token=token&oauth_callback=https%3A%2F%2Fcallback" + ) + assert ( + cl.get_authorization_url() + == "https://www.upwork.com/services/api/auth?oauth_token=token" + ) + + def test_full_url(self): + assert client.full_url("/test/uri") == "https://www.upwork.com/api/test/uri" + assert ( + client.full_url("/test/uri", "gds") == "https://www.upwork.com/gds/test/uri" + ) + + def test_get_uri_with_format(self): + assert client.get_uri_with_format("/test/uri", "api") == "/test/uri.json" + assert client.get_uri_with_format("/test/uri", "gds") == "/test/uri" diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..bb745e7 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,17 @@ +from upwork import config + + +def test_config_initialization(): + cfg = config.Config( + { + "consumer_key": "keyxxxxxxxxxxxxxxxxxxxx", + "consumer_secret": "secretxxxxxxxxxx", + "access_token": "tokenxxxxxxxxxxxxxxxxxxxx", + "access_token_secret": "tokensecretxxxxx", + } + ) + + assert cfg.consumer_key == "keyxxxxxxxxxxxxxxxxxxxx" + assert cfg.consumer_secret == "secretxxxxxxxxxx" + assert cfg.access_token == "tokenxxxxxxxxxxxxxxxxxxxx" + assert cfg.access_token_secret == "tokensecretxxxxx" diff --git a/tests/test_upwork.py b/tests/test_upwork.py new file mode 100644 index 0000000..7631189 --- /dev/null +++ b/tests/test_upwork.py @@ -0,0 +1,5 @@ +from upwork import upwork + + +def test_upwork(): + assert upwork is not None diff --git a/upwork/__init__.py b/upwork/__init__.py new file mode 100644 index 0000000..7d437f0 --- /dev/null +++ b/upwork/__init__.py @@ -0,0 +1,11 @@ +"""Top-level package for python-upwork.""" + +from upwork.config import Config +from upwork.client import Client +from . import routers + +__author__ = """Maksym Novozhylov""" +__email__ = "mnovozhilov@upwork.com" +__version__ = "2.0.0" + +__all__ = ("Config", "Client", "routers") diff --git a/upwork/client.py b/upwork/client.py new file mode 100644 index 0000000..749d0ee --- /dev/null +++ b/upwork/client.py @@ -0,0 +1,209 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + +import requests +from . import upwork +from requests_oauthlib import OAuth1 # type: ignore +from urllib.parse import parse_qsl, urlencode + + +class Client(object): + """API client for OAuth1 authorization + + *Parameters:* + :config: An instance of upwork.Config class, which contains the configuration keys and tokens + """ + + __data_format = "json" + __overload_var = "http_method" + + __uri_auth = "/services/api/auth" + __uri_rtoken = "/auth/v1/oauth/token/request" + __uri_atoken = "/auth/v1/oauth/token/access" + + epoint = upwork.DEFAULT_EPOINT + + def __init__(self, config): + self.config = config + + def get_request_token(self): + """Get request token""" + oauth = OAuth1(self.config.consumer_key, self.config.consumer_secret) + request_token_url = full_url(self.__uri_rtoken, upwork.DEFAULT_EPOINT) + + try: + r = requests.post(url=request_token_url, auth=oauth) + except Exception as e: + raise e + + rtoken_response = dict(parse_qsl(r.content.decode("utf8"))) + self.request_token = rtoken_response.get("oauth_token") + self.request_token_secret = rtoken_response.get("oauth_token_secret") + + return self.request_token, self.request_token_secret + + def get_authorization_url(self, callback_url=None): + """Get authorization URL + + :param callback_url: (Default value = None) + + """ + oauth_token = ( + getattr(self, "request_token", None) or self.get_request_token()[0] + ) + + if callback_url: + params = urlencode( + {"oauth_token": oauth_token, "oauth_callback": callback_url} + ) + else: + params = urlencode({"oauth_token": oauth_token}) + + return "{0}{1}?{2}".format(upwork.BASE_HOST, self.__uri_auth, params) + + def get_access_token(self, verifier): + """Returns access token and access token secret + + :param verifier: + + """ + try: + request_token = self.request_token + request_token_secret = self.request_token_secret + except AttributeError as e: + raise Exception( + "Request token pair not found. You need to call get_authorization_url" + ) + + oauth = OAuth1( + self.config.consumer_key, + client_secret=self.config.consumer_secret, + resource_owner_key=self.request_token, + resource_owner_secret=self.request_token_secret, + verifier=verifier, + ) + + access_token_url = full_url(self.__uri_atoken, upwork.DEFAULT_EPOINT) + + try: + r = requests.post(url=access_token_url, auth=oauth) + except Exception as e: + raise e + + atoken_response = dict(parse_qsl(r.content.decode("utf8"))) + self.config.access_token = atoken_response.get("oauth_token") + self.config.access_token_secret = atoken_response.get("oauth_token_secret") + + return self.config.access_token, self.config.access_token_secret + + def get(self, uri, params=None): + """Execute GET request + + :param uri: + :param params: (Default value = None) + + """ + return self.send_request(uri, "get", params) + + def post(self, uri, params=None): + """Execute POST request + + :param uri: + :param params: (Default value = None) + + """ + return self.send_request(uri, "post", params) + + def put(self, uri, params=None): + """Execute PUT request + + :param uri: + :param params: (Default value = None) + + """ + return self.send_request(uri, "put", params) + + def delete(self, uri, params=None): + """Execute DELETE request + + :param uri: + :param params: (Default value = None) + + """ + return self.send_request(uri, "delete", params) + + def send_request(self, uri, method="get", params={}): + """Send request + + :param uri: + :param method: (Default value = 'get') + :param params: (Default value = {}) + + """ + # delete does not support passing the parameters + if method == "delete": + params[self.__overload_var] = method + + oauth = OAuth1( + self.config.consumer_key, + client_secret=self.config.consumer_secret, + resource_owner_key=self.config.access_token, + resource_owner_secret=self.config.access_token_secret, + signature_type="query", + ) + + url = full_url(get_uri_with_format(uri, self.epoint), self.epoint) + + if method == "get": + r = requests.get(url, params=params, auth=oauth) + elif method == "put": + headers = {"Content-type": "application/json"} + r = requests.put(url, json=params, headers=headers, auth=oauth) + elif method in {"post", "delete"}: + headers = {"Content-type": "application/json"} + r = requests.post(url, json=params, headers=headers, auth=oauth) + else: + raise ValueError( + 'Do not know how to handle http method "{0}"'.format(method) + ) + + return r.json() + + +""" + +""" + + +def full_url(uri, epoint=None): + """Get full URL + + :param uri: + :param epoint: (Default value = None) + + """ + if not epoint: + epoint = upwork.DEFAULT_EPOINT + return "{0}/{1}{2}".format(upwork.BASE_HOST, epoint, uri) + + +def get_uri_with_format(uri, epoint): + """Get URI with format ending + + :param uri: + :param epoint: + + """ + if epoint == upwork.DEFAULT_EPOINT: + uri += ".json" + return uri diff --git a/upwork/config.py b/upwork/config.py new file mode 100644 index 0000000..4690c5d --- /dev/null +++ b/upwork/config.py @@ -0,0 +1,28 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Config: + """Configuration container""" + + def __init__(self, config): + self.consumer_key, self.consumer_secret = ( + config["consumer_key"], + config["consumer_secret"], + ) + + if "access_token" in config: + self.access_token = config["access_token"] + + if "access_token_secret" in config: + self.access_token_secret = config["access_token_secret"] diff --git a/upwork/routers/__init__.py b/upwork/routers/__init__.py new file mode 100644 index 0000000..f3ade5c --- /dev/null +++ b/upwork/routers/__init__.py @@ -0,0 +1,20 @@ +"""routers""" +from . import activities, auth, freelancers, hr, jobs +from . import messages, metadata, organization, payments +from . import reports, snapshots, workdays, workdiary + +__all__ = ( + "activities", + "auth", + "freelancers", + "hr", + "jobs", + "messages", + "metadata", + "organization", + "payments", + "reports", + "snapshots", + "workdays", + "workdiary" +) diff --git a/upwork/routers/activities/__init__.py b/upwork/routers/activities/__init__.py new file mode 100644 index 0000000..fe31729 --- /dev/null +++ b/upwork/routers/activities/__init__.py @@ -0,0 +1 @@ +"""routers""" diff --git a/upwork/routers/activities/engagement.py b/upwork/routers/activities/engagement.py new file mode 100644 index 0000000..2799c79 --- /dev/null +++ b/upwork/routers/activities/engagement.py @@ -0,0 +1,44 @@ +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_specific(self, engagement_ref): + """List activities for specific engagement + + :param engagement_ref: String + + """ + return self.client.get("/tasks/v2/tasks/contracts/{0}".format(engagement_ref)) + + def assign(self, company, team, engagement, params): + """Assign engagements to the list of activities + + Parameters: + :param company: + :param team: + :param engagement: + :param params: + + """ + return self.client.put( + "/otask/v1/tasks/companies/{0}/teams/{1}/engagements/{2}/tasks".format( + company, team, engagement + ), + params, + ) + + def assign_to_engagement(self, engagement_ref, params): + """Assign to specific engagement the list of activities + + Parameters: + :param engagement_ref: + :param params: + + """ + return self.client.put( + "/tasks/v2/tasks/contracts/{0}".format(engagement_ref), params + ) diff --git a/upwork/routers/activities/team.py b/upwork/routers/activities/team.py new file mode 100644 index 0000000..7f5d818 --- /dev/null +++ b/upwork/routers/activities/team.py @@ -0,0 +1,122 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_list(self, company, team): + """List all oTask/Activity records within a team + + :param company: String + :param team: String + + """ + return self.__get_by_type(company, team) + + def get_specific_list(self, company, team, code): + """List all oTask/Activity records within a Company by specified code(s) + + :param company: String + :param team: String + :param code: String + + """ + return self.__get_by_type(company, team, code) + + def add_activity(self, company, team, params): + """Create an oTask/Activity record within a team + + Parameters: + :param company: + :param team: + :param params: + + """ + return self.client.post( + "/otask/v1/tasks/companies/{0}/teams/{1}/tasks".format(company, team), + params, + ) + + def update_activities(self, company, team, code, params): + """Update specific oTask/Activity record within a team + + Parameters: + :param company: + :param team: + :param code: + :param params: + + """ + return self.client.put( + "/otask/v1/tasks/companies/{0}/teams/{1}/tasks/{2}".format( + company, team, code + ), + params, + ) + + def archive_activities(self, company, team, code): + """Archive specific oTask/Activity record within a team + + :param company: String + :param team: String + :param code: String + + """ + return self.client.put( + "/otask/v1/tasks/companies/{0}/teams/{1}/archive/{2}".format( + company, team, code + ) + ) + + def unarchive_activities(self, company, team, code): + """Unarchive specific oTask/Activity record within a team + + :param company: String + :param team: String + :param code: String + + """ + return self.client.put( + "/otask/v1/tasks/companies/{0}/teams/{1}/unarchive/{2}".format( + company, team, code + ) + ) + + def update_batch(self, company, params): + """Update a group of oTask/Activity records within a company + + Parameters: + :param company: + :param params: + + """ + return self.client.put( + "/otask/v1/tasks/companies/{0}/tasks/batch".format(company), params + ) + + def __get_by_type(self, company, team, code=None): + url = "" + if code is not None: + url = "/" + code + + return self.client.get( + "/otask/v1/tasks/companies/{0}/teams/{1}/tasks{2}".format( + company, team, url + ) + ) diff --git a/upwork/routers/auth.py b/upwork/routers/auth.py new file mode 100644 index 0000000..b3d1087 --- /dev/null +++ b/upwork/routers/auth.py @@ -0,0 +1,25 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_user_info(self): + """Get info of authenticated user""" + return self.client.get("/auth/v1/info") diff --git a/upwork/routers/freelancers/__init__.py b/upwork/routers/freelancers/__init__.py new file mode 100644 index 0000000..fac6e3c --- /dev/null +++ b/upwork/routers/freelancers/__init__.py @@ -0,0 +1,4 @@ +"""routers""" +from . import profile, search + +__all__ = ("profile", "search") diff --git a/upwork/routers/freelancers/profile.py b/upwork/routers/freelancers/profile.py new file mode 100644 index 0000000..8576d6c --- /dev/null +++ b/upwork/routers/freelancers/profile.py @@ -0,0 +1,37 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_specific(self, key): + """Get specific profile + + :param key: String + + """ + return self.client.get("/profiles/v1/providers/{0}".format(key)) + + def get_specific_brief(self, key): + """Get brief info on specific profile + + :param key: String + + """ + return self.client.get("/profiles/v1/providers/{0}/brief".format(key)) diff --git a/upwork/routers/freelancers/search.py b/upwork/routers/freelancers/search.py new file mode 100644 index 0000000..de28372 --- /dev/null +++ b/upwork/routers/freelancers/search.py @@ -0,0 +1,31 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def find(self, params): + """Search profiles + + Parameters: + + :param params: + + """ + return self.client.get("/profiles/v2/search/providers", params) diff --git a/upwork/routers/hr/__init__.py b/upwork/routers/hr/__init__.py new file mode 100644 index 0000000..c7d6193 --- /dev/null +++ b/upwork/routers/hr/__init__.py @@ -0,0 +1,15 @@ +"""routers""" +from . import clients, contracts, engagements, freelancers +from . import interviews, jobs, milestones, roles, submissions + +__all__ = ( + "clients", + "contracts", + "engagements", + "freelancers", + "interviews", + "jobs", + "milestones", + "roles", + "submissions" +) diff --git a/upwork/routers/hr/clients/__init__.py b/upwork/routers/hr/clients/__init__.py new file mode 100644 index 0000000..fe31729 --- /dev/null +++ b/upwork/routers/hr/clients/__init__.py @@ -0,0 +1 @@ +"""routers""" diff --git a/upwork/routers/hr/clients/applications.py b/upwork/routers/hr/clients/applications.py new file mode 100644 index 0000000..25c6b66 --- /dev/null +++ b/upwork/routers/hr/clients/applications.py @@ -0,0 +1,44 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_list(self, params): + """Get list of applications + + Parameters: + + :param params: + + """ + return self.client.get("/hr/v4/clients/applications", params) + + def get_specific(self, reference, params): + """Get specific application + + Parameters: + + :param reference: + :param params: + + """ + return self.client.get( + "/hr/v4/clients/applications/{0}".format(reference), params + ) diff --git a/upwork/routers/hr/clients/offers.py b/upwork/routers/hr/clients/offers.py new file mode 100644 index 0000000..6967ce2 --- /dev/null +++ b/upwork/routers/hr/clients/offers.py @@ -0,0 +1,54 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_list(self, params): + """Get list of offers + + Parameters: + + :param params: + + """ + return self.client.get("/offers/v1/clients/offers", params) + + def get_specific(self, reference, params): + """Get specific offer + + Parameters: + + :param reference: + :param params: + + """ + return self.client.get( + "/offers/v1/clients/offers/{0}".format(reference), params + ) + + def make_offer(self, params): + """Make an Offer + + Parameters: + + :param params: + + """ + return self.client.post("/offers/v1/clients/offers", params) diff --git a/upwork/routers/hr/contracts.py b/upwork/routers/hr/contracts.py new file mode 100644 index 0000000..3432150 --- /dev/null +++ b/upwork/routers/hr/contracts.py @@ -0,0 +1,54 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def suspend_contract(self, reference, params): + """Suspend Contract + + Parameters: + + :param reference: + :param params: + + """ + return self.client.put("/hr/v2/contracts/{0}/suspend".format(reference), params) + + def restart_contract(self, reference, params): + """Restart Contract + + Parameters: + + :param reference: + :param params: + + """ + return self.client.put("/hr/v2/contracts/{0}/restart".format(reference), params) + + def end_contract(self, reference, params): + """End Contract + + Parameters: + + :param reference: + :param params: + + """ + return self.client.delete("/hr/v2/contracts/{0}".format(reference), params) diff --git a/upwork/routers/hr/engagements.py b/upwork/routers/hr/engagements.py new file mode 100644 index 0000000..2012c1d --- /dev/null +++ b/upwork/routers/hr/engagements.py @@ -0,0 +1,39 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_list(self, params): + """Get list of engagements + + Parameters: + + :param params: + + """ + return self.client.get("/hr/v2/engagements", params) + + def get_specific(self, reference): + """Get specific engagement + + :param reference: String + + """ + return self.client.get("/hr/v2/engagements/{0}".format(reference)) diff --git a/upwork/routers/hr/freelancers/__init__.py b/upwork/routers/hr/freelancers/__init__.py new file mode 100644 index 0000000..5631897 --- /dev/null +++ b/upwork/routers/hr/freelancers/__init__.py @@ -0,0 +1,4 @@ +"""routers""" +from . import applications, offers + +__all__ = ("applications", "offers") diff --git a/upwork/routers/hr/freelancers/applications.py b/upwork/routers/hr/freelancers/applications.py new file mode 100644 index 0000000..2b9fc73 --- /dev/null +++ b/upwork/routers/hr/freelancers/applications.py @@ -0,0 +1,39 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_list(self, params={}): + """Get list of applications + + Parameters: + + :param params: (Default value = {}) + + """ + return self.client.get("/hr/v4/contractors/applications", params) + + def get_specific(self, reference): + """Get specific application + + :param reference: String + + """ + return self.client.get("/hr/v4/contractors/applications/{0}".format(reference)) diff --git a/upwork/routers/hr/freelancers/offers.py b/upwork/routers/hr/freelancers/offers.py new file mode 100644 index 0000000..e5b8906 --- /dev/null +++ b/upwork/routers/hr/freelancers/offers.py @@ -0,0 +1,52 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_list(self, params={}): + """Get list of offers + + Parameters: + + :param params: (Default value = {}) + + """ + return self.client.get("/offers/v1/contractors/offers", params) + + def get_specific(self, reference): + """Get specific offer + + :param reference: String + + """ + return self.client.get("/offers/v1/contractors/offers/{0}".format(reference)) + + def actions(self, reference, params): + """Make an Offer + + Parameters: + + :param reference: + :param params: + + """ + return self.client.post( + "/offers/v1/contractors/actions/{0}".format(reference), params + ) diff --git a/upwork/routers/hr/interviews.py b/upwork/routers/hr/interviews.py new file mode 100644 index 0000000..bda6b36 --- /dev/null +++ b/upwork/routers/hr/interviews.py @@ -0,0 +1,32 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def invite(self, job_key, params): + """Invite to Interview + + Parameters: + + :param job_key: + :param params: + + """ + return self.client.post("/hr/v1/jobs/{0}/candidates".format(job_key), params) diff --git a/upwork/routers/hr/jobs.py b/upwork/routers/hr/jobs.py new file mode 100644 index 0000000..e6682d6 --- /dev/null +++ b/upwork/routers/hr/jobs.py @@ -0,0 +1,71 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_list(self, params): + """Get list of jobs + + Parameters: + + :param params: + + """ + return self.client.get("/hr/v2/jobs", params) + + def get_specific(self, key): + """Get specific job by key + + :param key: String + + """ + return self.client.get("/hr/v2/jobs/{0}".format(key)) + + def post_job(self, params): + """Post a new job + + Parameters: + + :param params: + + """ + return self.client.post("/hr/v2/jobs", params) + + def edit_job(self, key, params): + """Edit existent job + + Parameters: + + :param key: + :param params: + + """ + self.client.put("/hr/v2/jobs/{0}".format(key), params) + + def delete_job(self, key, params): + """Delete existent job + + Parameters: + + :param key: + :param params: + + """ + self.client.delete("/hr/v2/jobs/{0}".format(key), params) diff --git a/upwork/routers/hr/milestones.py b/upwork/routers/hr/milestones.py new file mode 100644 index 0000000..f9346ae --- /dev/null +++ b/upwork/routers/hr/milestones.py @@ -0,0 +1,96 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_active_milestone(self, contract_id): + """Get active Milestone for specific Contract + + :param contract_id: String + + """ + return self.client.get( + "/hr/v3/fp/milestones/statuses/active/contracts/{0}".format(contract_id) + ) + + def get_submissions(self, milestone_id): + """Get active Milestone for specific Contract + + :param milestone_id: String + + """ + return self.client.get( + "/hr/v3/fp/milestones/{0}/submissions".format(milestone_id) + ) + + def create(self, params): + """Create a new Milestone + + Parameters: + + :param params: + + """ + return self.client.post("/hr/v3/fp/milestones", params) + + def edit(self, milestone_id, params): + """Edit an existing Milestone + + Parameters: + + :param milestone_id: + :param params: + + """ + return self.client.put("/hr/v3/fp/milestones/{0}".format(milestone_id), params) + + def activate(self, milestone_id, params): + """Activate an existing Milestone + + Parameters: + + :param milestone_id: + :param params: + + """ + return self.client.put( + "/hr/v3/fp/milestones/{0}/activate".format(milestone_id), params + ) + + def approve(self, milestone_id, params): + """Approve an existing Milestone + + Parameters: + + :param milestone_id: + :param params: + + """ + return self.client.put( + "/hr/v3/fp/milestones/{0}/approve".format(milestone_id), params + ) + + def delete(self, milestone_id): + """Delete an existing Milestone + + :param milestone_id: String + + """ + return self.client.delete("/hr/v3/fp/milestones/{0}".format(milestone_id)) diff --git a/upwork/routers/hr/roles.py b/upwork/routers/hr/roles.py new file mode 100644 index 0000000..3c05c3f --- /dev/null +++ b/upwork/routers/hr/roles.py @@ -0,0 +1,34 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_all(self): + """Get user roles""" + return self.client.get("/hr/v2/userroles") + + def get_by_specific_user(self, user_reference): + """Get by specific user + + :param reference: String + :param user_reference: + + """ + return self.client.get("/hr/v2/userroles/{0}".format(user_reference)) diff --git a/upwork/routers/hr/submissions.py b/upwork/routers/hr/submissions.py new file mode 100644 index 0000000..6c1234c --- /dev/null +++ b/upwork/routers/hr/submissions.py @@ -0,0 +1,57 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def request_approval(self, params): + """Freelancer submits work for the client to approve + + Parameters: + + :param params: + + """ + return self.client.post("/hr/v3/fp/submissions", params) + + def approve(self, submission_id, params): + """Approve an existing Submission + + Parameters: + + :param submission_id: + :param params: + + """ + return self.client.put( + "/hr/v3/fp/submissions/{0}/approve".format(submission_id), params + ) + + def reject(self, submission_id, params): + """Reject an existing Submission + + Parameters: + + :param submission_id: + :param params: + + """ + return self.client.put( + "/hr/v3/fp/submissions/{0}/reject".format(submission_id), params + ) diff --git a/upwork/routers/jobs/__init__.py b/upwork/routers/jobs/__init__.py new file mode 100644 index 0000000..fac6e3c --- /dev/null +++ b/upwork/routers/jobs/__init__.py @@ -0,0 +1,4 @@ +"""routers""" +from . import profile, search + +__all__ = ("profile", "search") diff --git a/upwork/routers/jobs/profile.py b/upwork/routers/jobs/profile.py new file mode 100644 index 0000000..d3e7283 --- /dev/null +++ b/upwork/routers/jobs/profile.py @@ -0,0 +1,29 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_specific(self, key): + """Get specific profile + + :param key: String + + """ + return self.client.get("/profiles/v1/jobs/{0}".format(key)) diff --git a/upwork/routers/jobs/search.py b/upwork/routers/jobs/search.py new file mode 100644 index 0000000..82c9524 --- /dev/null +++ b/upwork/routers/jobs/search.py @@ -0,0 +1,31 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def find(self, params): + """Search profiles + + Parameters: + + :param params: + + """ + return self.client.get("/profiles/v2/search/jobs", params) diff --git a/upwork/routers/messages.py b/upwork/routers/messages.py new file mode 100644 index 0000000..75ebcfb --- /dev/null +++ b/upwork/routers/messages.py @@ -0,0 +1,148 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_rooms(self, company, params={}): + """Retrieve rooms information + + Parameters: + :param company: + :param params: (Default value = {}) + + """ + return self.client.get("/messages/v3/{0}/rooms".format(company), params) + + def get_room_details(self, company, room_id, params={}): + """Get a specific room information + + Parameters: + :param company: + :param room_id: + :param params: (Default value = {}) + + """ + return self.client.get( + "/messages/v3/{0}/rooms/{1}".format(company, room_id), params + ) + + def get_room_messages(self, company, room_id, params={}): + """Get messages from a specific room + + Parameters: + :param company: + :param room_id: + :param params: (Default value = {}) + + """ + return self.client.get( + "/messages/v3/{0}/rooms/{1}/stories".format(company, room_id), params + ) + + def get_room_by_offer(self, company, offer_id, params={}): + """Get a specific room by offer ID + + Parameters: + :param company: + :param offer_id: + :param params: (Default value = {}) + + """ + return self.client.get( + "/messages/v3/{0}/rooms/offers/{1}".format(company, offer_id), params + ) + + def get_room_by_application(self, company, application_id, params={}): + """Get a specific room by application ID + + Parameters: + :param company: + :param application_id: + :param params: (Default value = {}) + + """ + return self.client.get( + "/messages/v3/{0}/rooms/applications/{1}".format(company, application_id), + params, + ) + + def get_room_by_contract(self, company, contract_id, params={}): + """Get a specific room by contract ID + + Parameters: + :param company: + :param contract_id: + :param params: (Default value = {}) + + """ + return self.client.get( + "/messages/v3/{0}/rooms/contracts/{1}".format(company, contract_id), params + ) + + def create_room(self, company, params={}): + """Create a new room + + Parameters: + :param company: + :param params: (Default value = {}) + + """ + return self.client.post("/messages/v3/{0}/rooms".format(company), params) + + def send_message_to_room(self, company, room_id, params={}): + """Send a message to a room + + Parameters: + :param company: + :param room_id: + :param params: (Default value = {}) + + """ + return self.client.post( + "/messages/v3/{0}/rooms/{1}/stories".format(company, room_id), params + ) + + def update_room_settings(self, company, room_id, username, params={}): + """Update a room settings + + Parameters: + :param company: + :param room_id: + :param username: + :param params: (Default value = {}) + + """ + return self.client.put( + "/messages/v3/{0}/rooms/{1}/users/{2}".format(company, room_id, username), + params, + ) + + def update_room_metadata(self, company, room_id, params={}): + """Update the metadata of a room + + Parameters: + :param company: + :param room_id: + :param params: (Default value = {}) + + """ + return self.client.put( + "/messages/v3/{0}/rooms/{1}".format(company, room_id), params + ) diff --git a/upwork/routers/metadata.py b/upwork/routers/metadata.py new file mode 100644 index 0000000..52c0a93 --- /dev/null +++ b/upwork/routers/metadata.py @@ -0,0 +1,57 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_categories_v2(self): + """Get categories (V2)""" + return self.client.get("/profiles/v2/metadata/categories") + + def get_skills(self): + """Get skills""" + return self.client.get("/profiles/v1/metadata/skills") + + def get_skills_v2(self, params): + """Get skills (V2) + + :param params: + + """ + return self.client.get("/profiles/v2/metadata/skills", params) + + def get_specialties(self): + """Get specialties""" + return self.client.get("/profiles/v1/metadata/specialties") + + def get_regions(self): + """Get regions""" + return self.client.get("/profiles/v1/metadata/regions") + + def get_tests(self): + """Get tests""" + return self.client.get("/profiles/v1/metadata/tests") + + def get_reasons(self, params): + """Get reasons + + :param params: + + """ + return self.client.get("/profiles/v1/metadata/reasons", params) diff --git a/upwork/routers/organization/__init__.py b/upwork/routers/organization/__init__.py new file mode 100644 index 0000000..7afe2c1 --- /dev/null +++ b/upwork/routers/organization/__init__.py @@ -0,0 +1,4 @@ +"""routers""" +from . import companies, teams, users + +__all__ = ("companies", "teams", "users") diff --git a/upwork/routers/organization/companies.py b/upwork/routers/organization/companies.py new file mode 100644 index 0000000..2e285c6 --- /dev/null +++ b/upwork/routers/organization/companies.py @@ -0,0 +1,49 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_list(self): + """Get Companies Info""" + return self.client.get("/hr/v2/companies") + + def get_specific(self, company_reference): + """Get Specific Company + + :param company_reference: String + + """ + return self.client.get("/hr/v2/companies/{0}".format(company_reference)) + + def get_teams(self, company_reference): + """Get Teams in Company + + :param company_reference: String + + """ + return self.client.get("/hr/v2/companies/{0}/teams".format(company_reference)) + + def get_users(self, company_reference): + """Get Users in Company + + :param company_reference: String + + """ + return self.client.get("/hr/v2/companies/{0}/users".format(company_reference)) diff --git a/upwork/routers/organization/teams.py b/upwork/routers/organization/teams.py new file mode 100644 index 0000000..04b507b --- /dev/null +++ b/upwork/routers/organization/teams.py @@ -0,0 +1,33 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_list(self): + """Get Teams""" + return self.client.get("/hr/v2/teams") + + def get_users_in_team(self, team_reference): + """Get Users in Team + + :param team_reference: String + + """ + return self.client.get("/hr/v2/teams/{0}/users".format(team_reference)) diff --git a/upwork/routers/organization/users.py b/upwork/routers/organization/users.py new file mode 100644 index 0000000..421825e --- /dev/null +++ b/upwork/routers/organization/users.py @@ -0,0 +1,33 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_my_info(self): + """Get Auth user info""" + return self.client.get("/hr/v2/users/me") + + def get_specific(self, user_reference): + """Get Specific User Info + + :param user_reference: String + + """ + return self.client.get("/hr/v2/users/{0}".format(user_reference)) diff --git a/upwork/routers/payments.py b/upwork/routers/payments.py new file mode 100644 index 0000000..2361a5c --- /dev/null +++ b/upwork/routers/payments.py @@ -0,0 +1,32 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def submit_bonus(self, team_reference, params): + """Submit bonus + + :param team_reference: String + :param params: + + """ + return self.client.post( + "/hr/v2/teams/{0}/adjustments".format(team_reference), params + ) diff --git a/upwork/routers/reports/__init__.py b/upwork/routers/reports/__init__.py new file mode 100644 index 0000000..ea001d6 --- /dev/null +++ b/upwork/routers/reports/__init__.py @@ -0,0 +1,4 @@ +"""routers""" +from . import finance, time + +__all__ = ("finance", "time") diff --git a/upwork/routers/reports/finance/__init__.py b/upwork/routers/reports/finance/__init__.py new file mode 100644 index 0000000..b3bcc21 --- /dev/null +++ b/upwork/routers/reports/finance/__init__.py @@ -0,0 +1,4 @@ +"""routers""" +from . import accounts, billings, earnings + +__all__ = ("accounts", "billings", "earnings") diff --git a/upwork/routers/reports/finance/accounts.py b/upwork/routers/reports/finance/accounts.py new file mode 100644 index 0000000..88dd0bb --- /dev/null +++ b/upwork/routers/reports/finance/accounts.py @@ -0,0 +1,50 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Gds: + """ """ + + client = None + entry_point = "gds" + + def __init__(self, client): + self.client = client + self.client.epoint = self.entry_point + + def get_owned(self, freelancer_reference, params): + """Generate Financial Reports for an owned Account + + Arguments: + + :param freelancer_reference: param params: + :param params: + + """ + return self.client.get( + "/finreports/v2/financial_account_owner/{0}".format(freelancer_reference), + params, + ) + + def get_specific(self, entity_reference, params): + """Generate Financial Reports for a Specific Account + + Arguments: + + :param entity_reference: param params: + :param params: + + """ + return self.client.get( + "/finreports/v2/financial_accounts/{0}".format(entity_reference), params + ) diff --git a/upwork/routers/reports/finance/billings.py b/upwork/routers/reports/finance/billings.py new file mode 100644 index 0000000..c803b16 --- /dev/null +++ b/upwork/routers/reports/finance/billings.py @@ -0,0 +1,98 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Gds: + """ """ + + client = None + entry_point = "gds" + + def __init__(self, client): + self.client = client + self.client.epoint = self.entry_point + + def get_by_freelancer(self, freelancer_reference, params): + """Generate Billing Reports for a Specific Freelancer + + Parameters: + + :param freelancer_reference: + :param params: + + """ + return self.client.get( + "/finreports/v2/providers/{0}/billings".format(freelancer_reference), params + ) + + def get_by_freelancers_team(self, freelancer_team_reference, params): + """Generate Billing Reports for a Specific Freelancer's Team + + Parameters: + + :param freelancer_team_reference: + :param params: + + """ + return self.client.get( + "/finreports/v2/provider_teams/{0}/billings".format( + freelancer_team_reference + ), + params, + ) + + def get_by_freelancers_company(self, freelancer_company_reference, params): + """Generate Billing Reports for a Specific Freelancer's Company + + Parameters: + + :param freelancer_company_reference: + :param params: + + """ + return self.client.get( + "/finreports/v2/provider_companies/{0}/billings".format( + freelancer_company_reference + ), + params, + ) + + def get_by_buyers_team(self, buyer_team_reference, params): + """Generate Billing Reports for a Specific Buyer's Team + + Parameters: + + :param buyer_team_reference: + :param params: + + """ + return self.client.get( + "/finreports/v2/buyer_teams/{0}/billings".format(buyer_team_reference), + params, + ) + + def get_by_buyers_company(self, buyer_company_reference, params): + """Generate Billing Reports for a Specific Buyer's Company + + Parameters: + + :param buyer_company_reference: + :param params: + + """ + return self.client.get( + "/finreports/v2/buyer_companies/{0}/billings".format( + buyer_company_reference + ), + params, + ) diff --git a/upwork/routers/reports/finance/earnings.py b/upwork/routers/reports/finance/earnings.py new file mode 100644 index 0000000..dcfbf29 --- /dev/null +++ b/upwork/routers/reports/finance/earnings.py @@ -0,0 +1,98 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Gds: + """ """ + + client = None + entry_point = "gds" + + def __init__(self, client): + self.client = client + self.client.epoint = self.entry_point + + def get_by_freelancer(self, freelancer_reference, params): + """Generate Earning Reports for a Specific Freelancer + + Parameters: + + :param freelancer_reference: + :param params: + + """ + return self.client.get( + "/finreports/v2/providers/{0}/earnings".format(freelancer_reference), params + ) + + def get_by_freelancers_team(self, freelancer_team_reference, params): + """Generate Earning Reports for a Specific Freelancer's Team + + Parameters: + + :param freelancer_team_reference: + :param params: + + """ + return self.client.get( + "/finreports/v2/provider_teams/{0}/earnings".format( + freelancer_team_reference + ), + params, + ) + + def get_by_freelancers_company(self, freelancer_company_reference, params): + """Generate Earning Reports for a Specific Freelancer's Company + + Parameters: + + :param freelancer_company_reference: + :param params: + + """ + return self.client.get( + "/finreports/v2/provider_companies/{0}/earnings".format( + freelancer_company_reference + ), + params, + ) + + def get_by_buyers_team(self, buyer_team_reference, params): + """Generate Earning Reports for a Specific Buyer's Team + + Parameters: + + :param buyer_team_reference: + :param params: + + """ + return self.client.get( + "/finreports/v2/buyer_teams/{0}/earnings".format(buyer_team_reference), + params, + ) + + def get_by_buyers_company(self, buyer_company_reference, params): + """Generate Earning Reports for a Specific Buyer's Company + + Parameters: + + :param buyer_company_reference: + :param params: + + """ + return self.client.get( + "/finreports/v2/buyer_companies/{0}/earnings".format( + buyer_company_reference + ), + params, + ) diff --git a/upwork/routers/reports/time.py b/upwork/routers/reports/time.py new file mode 100644 index 0000000..f848f2a --- /dev/null +++ b/upwork/routers/reports/time.py @@ -0,0 +1,109 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Gds: + """ """ + + client = None + entry_point = "gds" + + def __init__(self, client): + self.client = client + self.client.epoint = self.entry_point + + def get_by_team_full(self, company, team, params): + """Generate Time Reports for a Specific Team (with financial info) + + Parameters: + + :param company: + :param team: + :param params: + + """ + return self.__get_by_type(company, team, None, params, False) + + def get_by_team_limited(self, company, team, params): + """Generate Time Reports for a Specific Team (hide financial info) + + Parameters: + + :param company: + :param team: + :param params: + + """ + return self.__get_by_type(company, team, None, params, True) + + def get_by_agency(self, company, agency, params): + """Generating Agency Specific Reports + + Parameters: + + :param company: + :param agency: + :param params: + + """ + return self.__get_by_type(company, None, agency, params, False) + + def get_by_company(self, company, params): + """Generating Company Wide Reports + + Parameters: + + :param company: + :param params: + + """ + return self.__get_by_type(company, None, None, params, False) + + def get_by_freelancer_limited(self, freelancer_id, params): + """Generating Freelancer's Specific Reports (hide financial info) + + Parameters: + + :param freelancer_id: + :param params: + + """ + return self.client.get( + "/timereports/v1/providers/{0}/hours".format(freelancer_id), params + ) + + def get_by_freelancer_full(self, freelancer_id, params): + """Generating Freelancer's Specific Reports (with financial info) + + Parameters: + + :param freelancer_id: + :param params: + + """ + return self.client.get( + "/timereports/v1/providers/{0}".format(freelancer_id), params + ) + + def __get_by_type(self, company, team, agency, params, hide_fin_data): + url = "" + if team is not None: + url = "/teams/{0}".format(team) + if hide_fin_data: + url = url + "/hours" + elif agency is not None: + url = "/agencies/{0}".format(agency) + + return self.client.get( + "/timereports/v1/companies/{0}{1}".format(company, url), params + ) diff --git a/upwork/routers/snapshots.py b/upwork/routers/snapshots.py new file mode 100644 index 0000000..97fade1 --- /dev/null +++ b/upwork/routers/snapshots.py @@ -0,0 +1,56 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_by_contract(self, contract, ts): + """Get snapshot info by specific contract + + :param contract: String + :param ts: String + + """ + return self.client.get( + "/team/v3/snapshots/contracts/{0}/{1}".format(contract, ts) + ) + + def update_by_contract(self, contract, ts, params): + """Update snapshot by specific contract + + Parameters: + :param contract: String + :param ts: String + :param params: + + """ + return self.client.put( + "/team/v3/snapshots/contracts/{0}/{1}".format(contract, ts), params + ) + + def delete_by_contract(self, contract, ts): + """Delete snapshot by specific contract + + :param contract: String + :param ts: String + + """ + return self.client.delete( + "/team/v3/snapshots/contracts/{0}/{1}".format(contract, ts) + ) diff --git a/upwork/routers/workdays.py b/upwork/routers/workdays.py new file mode 100644 index 0000000..ab096b7 --- /dev/null +++ b/upwork/routers/workdays.py @@ -0,0 +1,55 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_by_company(self, company, from_date, till_date, params={}): + """Get Workdays by Company + + Parameters: + :param company: + :param from_date: + :param till_date: + :param params: (Default value = {}) + + """ + return self.client.get( + "/team/v3/workdays/companies/{0}/{1},{2}".format( + company, from_date, till_date + ), + params, + ) + + def get_by_contract(self, contract, from_date, till_date, params={}): + """Get Workdays by Contract + + Parameters: + :param contract: + :param from_date: + :param till_date: + :param params: (Default value = {}) + + """ + return self.client.get( + "/team/v3/workdays/contracts/{0}/{1},{2}".format( + contract, from_date, till_date + ), + params, + ) diff --git a/upwork/routers/workdiary.py b/upwork/routers/workdiary.py new file mode 100644 index 0000000..6bf0338 --- /dev/null +++ b/upwork/routers/workdiary.py @@ -0,0 +1,47 @@ +# Licensed under the Upwork's API Terms of Use; +# you may not use this file except in compliance with the Terms. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author:: Maksym Novozhylov (mnovozhilov@upwork.com) +# Copyright:: Copyright 2020(c) Upwork.com +# License:: See LICENSE.txt and TOS - https://developers.upwork.com/api-tos.html + + +class Api: + """ """ + + client = None + + def __init__(self, client): + self.client = client + + def get_workdiary(self, company, date, params={}): + """Get Workdiary by Company + + Parameters: + :param company: + :param date: + :param params: (Default value = {}) + + """ + return self.client.get( + "/team/v3/workdiaries/companies/{0}/{1}".format(company, date), params + ) + + def get_by_contract(self, contract, date, params={}): + """Get Work Diary by Contract + + Parameters: + :param contract: + :param date: + :param params: (Default value = {}) + + """ + return self.client.get( + "/team/v3/workdiaries/contracts/{0}/{1}".format(contract, date), params + ) diff --git a/upwork/upwork.py b/upwork/upwork.py new file mode 100644 index 0000000..da6913d --- /dev/null +++ b/upwork/upwork.py @@ -0,0 +1,4 @@ +"""Main module.""" + +BASE_HOST = "https://www.upwork.com" +DEFAULT_EPOINT = "api" From e8cb46549b33525be8be3279d64d69534cec2f1e Mon Sep 17 00:00:00 2001 From: Maksym Novozhylov Date: Fri, 22 May 2020 15:19:51 +0200 Subject: [PATCH 3/6] Update Python requirements --- .travis.yml | 5 +---- README.md | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index c5e0468..9886908 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,12 +4,9 @@ cache: pip: true matrix: include: - - python: "3.7" - env: NOXSESSION="tests-3.7" - dist: xenial - python: "3.8" env: NOXSESSION="tests-3.8" - - python: "3.7" + - python: "3.8" env: NOXSESSION="lint" dist: xenial install: diff --git a/README.md b/README.md index c91d528..f5ad994 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ The usage of this API is ruled by the Terms of Use at: # Requirements To integrate this library you need to have: -* Python 3.7+ +* Python 3.8+ * requests_oauthlib >= 1.3.0 ## Installation diff --git a/setup.py b/setup.py index 976b65e..1af30f4 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( author="Maksym Novozhylov", author_email="mnovozhilov@upwork.com", - python_requires=">=3.7", + python_requires=">=3.8", classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", From 5aaa81901fa443a01ecfa975a22262a74290fe6f Mon Sep 17 00:00:00 2001 From: Maksym Novozhylov Date: Fri, 22 May 2020 15:22:05 +0200 Subject: [PATCH 4/6] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5ad994..cf1825e 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ To integrate this library you need to have: ## Installation - pip install python-upwork + pip3 install python-upwork All the dependencies will be automatically installed as well. From 9f24b93ef8b35394071cd772e4c908bfe02b47fb Mon Sep 17 00:00:00 2001 From: Maksym Novozhylov Date: Mon, 25 May 2020 12:10:21 +0200 Subject: [PATCH 5/6] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf1825e..dee2b10 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Python bindings for Upwork API ============ -[![License](http://img.shields.io/packagist/l/upwork/php-upwork.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) +[![License](http://img.shields.io/packagist/l/upwork/python-upwork.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) [![PyPI Version](https://badge.fury.io/py/python-upwork.svg)](http://badge.fury.io/py/python-upwork) [![GitHub release](https://img.shields.io/github/release/upwork/python-upwork.svg)](https://github.com/upwork/python-upwork/releases) [![Build Status](https://travis-ci.org/upwork/python-upwork.svg)](https://travis-ci.org/upwork/python-upwork) From 0d9ccb72bbbf06c6e9ea0d5b18c30e1ac2bf2e62 Mon Sep 17 00:00:00 2001 From: Maksym Novozhylov Date: Mon, 25 May 2020 12:14:16 +0200 Subject: [PATCH 6/6] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dee2b10..7234d3d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Python bindings for Upwork API ============ -[![License](http://img.shields.io/packagist/l/upwork/python-upwork.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) +[![License](https://img.shields.io/github/license/upwork/python-upwork)](http://www.apache.org/licenses/LICENSE-2.0.html) [![PyPI Version](https://badge.fury.io/py/python-upwork.svg)](http://badge.fury.io/py/python-upwork) [![GitHub release](https://img.shields.io/github/release/upwork/python-upwork.svg)](https://github.com/upwork/python-upwork/releases) [![Build Status](https://travis-ci.org/upwork/python-upwork.svg)](https://travis-ci.org/upwork/python-upwork)