├── tests ├── __init__.py ├── test_base.py └── test-confluence-attach.py ├── atlassian ├── VERSION ├── errors.py ├── __init__.py ├── request_utils.py ├── marketplace.py ├── crowd.py ├── portfolio.py ├── rest_client.py └── utils.py ├── docs ├── _config.yml ├── CNAME ├── _static │ └── favicon.ico ├── Makefile ├── make.bat ├── conf.py ├── xray.rst ├── bamboo.rst ├── index.rst ├── service_desk.rst ├── confluence.rst ├── jira.rst └── bitbucket.rst ├── requirements-dev.txt ├── MANIFEST.in ├── requirements.txt ├── setup.cfg ├── tox.ini ├── .pyup.yml ├── .whitesource ├── examples ├── bitbucket │ ├── bitbucket-users.py │ ├── bitbucket-fork-repository.py │ ├── bitbucket-changes-between-branches.py │ ├── bitbucket-project.py │ ├── bitbucket-pullrequest-tasks.py │ ├── bitbucket-projects-administrators.py │ ├── bitbucket-tag-manipulations.py │ ├── bitbucket-manage-pull-request.py │ ├── stash-user-auth-report.py │ ├── bitbucket-branch-restrictions.py │ ├── bitbucket-check-last-auth-users.py │ └── bitbucket-clean-jira-branches.py ├── jira │ ├── jira-add-comment.py │ ├── jira-edit-comment.py │ ├── jira-health-check.py │ ├── jira-jql-query.py │ ├── jira-update-project.py │ ├── jira-add-issues-to-sprint.py │ ├── jira-reindex.py │ ├── jira-sprint-rename.py │ ├── jira-tempo-account-customers.py │ ├── jira-create-issue.py │ ├── jira-tempo-accounts.py │ ├── jira-tempo-timesheet-worklogs.py │ ├── jira-project-security-level-checker.py │ ├── jira-project-administrators.py │ ├── jira-sprint-create.py │ ├── jira-copy-components-from-A-to-B.py │ ├── jira-custom-field-value-swaper.py │ ├── jira-add-components-to-all-projects.py │ ├── jira-convert-group-members-into-user-in-role.py │ ├── jira-admins-confluence-page.py │ ├── jira-project-leaders.py │ ├── jira-notification_schemes_duplicates.py │ ├── jira-find-screen-similarity.py │ └── jira-review-groups.py ├── confluence │ ├── confluence-page-create.py │ ├── confluence-reindex.py │ ├── confluence-page-update.py │ ├── confluence-get-user-details.py │ ├── confluence-page-get.py │ ├── confluence-get-space-content.py │ ├── confluence-get-space-permissions.py │ ├── confluence-nested-error.py │ ├── confluence-get-group-members.py │ ├── confluence-export-page.py │ ├── confluence-attach-file-as-link.py │ ├── confluence-export-pages.py │ ├── confluence-subtree-cleaner.py │ ├── confluence-page-properties.py │ ├── confluence-search-cql.py │ ├── confluence-check-unknown-attachment-error.py │ ├── confluence-draft-page-cleaner.py │ ├── confluence-trash-cleaner.py │ ├── confluence-copy-labels.py │ └── confluence-page-versions-cleaner.py ├── bamboo │ ├── bamboo-delete-build-result.py │ ├── bamboo-add-users.py │ ├── bamboo-plan-directory-info.py │ ├── bamboo-delete-plan-or-result.py │ ├── bamboo-activity.py │ ├── bamboo-add-plan-branch.py │ ├── bamboo-first-steps.py │ ├── bamboo-remove-unknown-status-build-results.py │ ├── bamboo-trigger-builds-console-app.py │ ├── bamboo-label-based-cleaner.py │ └── bamboo-remove-old-failed-results.py ├── README.md └── crowd │ ├── crowd-get-user.py │ └── crowd-get-group-nested-members.py ├── .travis.yml ├── .travis └── bump_version ├── .gitignore ├── setup.py ├── README.rst ├── CONTRIBUTING.rst └── LICENSE /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /atlassian/VERSION: -------------------------------------------------------------------------------- 1 | 1.17.1 2 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-dinky -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | atlassian-python-api.readthedocs.io -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | six 2 | requests 3 | oauthlib 4 | requests_oauthlib 5 | -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey-wenjun/atlassian-python-api/master/docs/_static/favicon.ico -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include atlassian/VERSION 2 | include LICENSE 3 | include README.rst 4 | include tox.ini 5 | include requirements.txt -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | six 2 | requests 3 | oauthlib 4 | requests_oauthlib 5 | kerberos; platform_system!='Windows' 6 | kerberos-sspi; platform_system=='Windows' -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pep8] 2 | max-line-length = 400 3 | ignore = E402,W391 4 | 5 | [aliases] 6 | test = pytest 7 | 8 | [metadata] 9 | description-file = README.rst -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py3 3 | skip_missing_interpreters = True 4 | 5 | [testenv] 6 | deps = pytest 7 | commands = pytest -v 8 | extras = kerberos 9 | -------------------------------------------------------------------------------- /.pyup.yml: -------------------------------------------------------------------------------- 1 | # autogenerated pyup.io config file 2 | # see https://pyup.io/docs/configuration/ for all available options 3 | 4 | schedule: every week 5 | update: insecure 6 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "checkRunSettings": { 3 | "vulnerableCheckRunConclusionLevel": "failure" 4 | }, 5 | "issueSettings": { 6 | "minSeverityLevel": "LOW" 7 | } 8 | } -------------------------------------------------------------------------------- /examples/bitbucket/bitbucket-users.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bitbucket 3 | 4 | 5 | bitbucket = Bitbucket( 6 | url='http://localhost:7990', 7 | username='admin', 8 | password='admin') 9 | 10 | data = bitbucket.get_users(user_filter="username") 11 | print(data) 12 | -------------------------------------------------------------------------------- /examples/jira/jira-add-comment.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | 4 | """ How to add comment""" 5 | 6 | jira = Jira( 7 | url="https://jira.example.com/", 8 | username='gonchik.tsymzhitov', 9 | password='admin') 10 | 11 | jira.issue_add_comment('TST-11098', 'test rest api request') 12 | -------------------------------------------------------------------------------- /examples/jira/jira-edit-comment.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | 4 | """ How to edit comment""" 5 | 6 | jira = Jira( 7 | url="https://jira.example.com/", 8 | username='gonchik.tsymzhitov', 9 | password='admin') 10 | 11 | jira.issue_edit_comment('TST-11098', 10700, 'edited test rest api request') 12 | -------------------------------------------------------------------------------- /examples/jira/jira-health-check.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | from pprint import pprint 4 | 5 | """ How to get server info with health check""" 6 | 7 | jira = Jira( 8 | url="https://jira.example.com/", 9 | username='admin', 10 | password='*******') 11 | 12 | pprint(jira.get_server_info(True)) 13 | -------------------------------------------------------------------------------- /examples/jira/jira-jql-query.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | 4 | 5 | JQL = 'project = DEMO AND status NOT IN (Closed, Resolved) ORDER BY issuekey' 6 | 7 | jira = Jira( 8 | url='http://localhost:8080', 9 | username='admin', 10 | password='admin') 11 | 12 | data = jira.jql(JQL) 13 | print(data) 14 | -------------------------------------------------------------------------------- /examples/jira/jira-update-project.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | 4 | jira = Jira( 5 | url='https://jira.example.com/', 6 | username='admin', 7 | password='admin' 8 | ) 9 | 10 | data = { 11 | 'permissionScheme': 10001 12 | } 13 | 14 | result = jira.update_project('RD', data) 15 | print(result) 16 | -------------------------------------------------------------------------------- /examples/confluence/confluence-page-create.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | 4 | confluence = Confluence( 5 | url='http://localhost:8090', 6 | username='admin', 7 | password='admin') 8 | 9 | status = confluence.create_page( 10 | space='DEMO', 11 | title='This is the title', 12 | body='This is the body') 13 | 14 | print(status) 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.3" 5 | - "3.4" 6 | - "3.5" 7 | - "3.6" 8 | - "3.7" 9 | - "3.8" 10 | - "3.9-dev" 11 | 12 | matrix: 13 | allow_failures: 14 | - python: "3.3" 15 | 16 | install: pip install tox 17 | before_script: 18 | - .travis/bump_version ./ minor > atlassian/VERSION 19 | script: 20 | - tox 21 | -------------------------------------------------------------------------------- /examples/jira/jira-add-issues-to-sprint.py: -------------------------------------------------------------------------------- 1 | from atlassian import Jira 2 | 3 | # Issues can be 1 or more 4 | issues_lst = ['APA-1', 'APA-2'] 5 | sprint_id = 103 6 | 7 | jira = Jira( 8 | url='http://localhost:8080', 9 | username='admin', 10 | password='admin') 11 | 12 | resp = jira.add_issues_to_sprint(sprint_id=sprint_id, 13 | issues=issues_lst) 14 | -------------------------------------------------------------------------------- /examples/bitbucket/bitbucket-fork-repository.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bitbucket 3 | 4 | bitbucket = Bitbucket( 5 | url='http://localhost:7990', 6 | username='admin', 7 | password='admin') 8 | 9 | data = bitbucket.fork_repository( 10 | project='DEMO', 11 | repository='example-repository', 12 | new_repository='forked-repository') 13 | 14 | print(data) 15 | -------------------------------------------------------------------------------- /examples/confluence/confluence-reindex.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | 4 | """ How to reindex the Confluence instance """ 5 | 6 | confluence = Confluence( 7 | url='http://localhost:8090', 8 | username='admin', 9 | password='admin') 10 | 11 | 12 | if confluence.reindex_get_status().get('finished'): 13 | print("Start reindex") 14 | confluence.reindex() 15 | -------------------------------------------------------------------------------- /examples/jira/jira-reindex.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from time import sleep 3 | from atlassian import Jira 4 | 5 | 6 | jira = Jira( 7 | url="http://localhost:8080/", 8 | username="jira-administrator", 9 | password="admin") 10 | 11 | jira.reindex() 12 | 13 | 14 | while not jira.reindex_status()['success']: 15 | print('Still reindexing...') 16 | sleep(1) 17 | 18 | print('Done.') 19 | -------------------------------------------------------------------------------- /examples/jira/jira-sprint-rename.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | 4 | jira = Jira( 5 | url='http://localhost:8080', 6 | username='admin', 7 | password='admin') 8 | 9 | data = jira.rename_sprint( 10 | sprint_id=10, 11 | name='Here is the name of my new sprint', 12 | start_date='2014-10-13 11:44', 13 | end_date='2014-10-20 09:34') 14 | 15 | print(data) 16 | -------------------------------------------------------------------------------- /examples/jira/jira-tempo-account-customers.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | from atlassian import Jira 3 | 4 | jira = Jira( 5 | url='http://localhost:8080', 6 | username='admin', 7 | password='admin') 8 | 9 | results = jira.tempo_account_get_customers() 10 | print("Count of Customers " + str(len(results))) 11 | 12 | for result in results: 13 | print(result.get('key'), ' ', result.get('name')) 14 | -------------------------------------------------------------------------------- /examples/confluence/confluence-page-update.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | 4 | confluence = Confluence( 5 | url='http://localhost:8090', 6 | username='admin', 7 | password='admin') 8 | 9 | status = confluence.update_page( 10 | parent_id=None, 11 | page_id=123456, 12 | title='This is the new title', 13 | body='This is the new body') 14 | 15 | print(status) 16 | -------------------------------------------------------------------------------- /examples/bitbucket/bitbucket-changes-between-branches.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bitbucket 3 | 4 | bitbucket = Bitbucket( 5 | url='http://localhost:7990', 6 | username='admin', 7 | password='admin') 8 | 9 | changelog = bitbucket.get_changelog( 10 | project='DEMO', 11 | repository='example-repository', 12 | ref_from='develop', 13 | ref_to='master') 14 | 15 | print(changelog) 16 | -------------------------------------------------------------------------------- /examples/confluence/confluence-get-user-details.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | 4 | """ This example related get user details and his status""" 5 | 6 | confluence = Confluence( 7 | url='http://localhost:8090', 8 | username='admin', 9 | password='admin') 10 | 11 | result = confluence.get_user_details_by_username(username="gonchik.tsymzhitov", expand="status") 12 | print(result) 13 | -------------------------------------------------------------------------------- /examples/jira/jira-create-issue.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | 4 | jira = Jira( 5 | url="https://jira.example.com/", 6 | username='gonchik.tsymzhitov', 7 | password='admin') 8 | 9 | jira.issue_create(fields={ 10 | 'project': {'key': 'TEST'}, 11 | 'issuetype': { 12 | "name": "Task" 13 | }, 14 | 'summary': 'test rest', 15 | 'description': 'rest rest', 16 | }) 17 | -------------------------------------------------------------------------------- /examples/jira/jira-tempo-accounts.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | 4 | jira = Jira( 5 | url='http://localhost:8080', 6 | username='admin', 7 | password='admin') 8 | 9 | accounts = jira.tempo_account_get_accounts_by_jira_project(project_id="10140") 10 | for account in accounts: 11 | print(account) 12 | jira.tempo_account_associate_with_jira_project(account['id'], project_id='10210') 13 | -------------------------------------------------------------------------------- /atlassian/errors.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | class ApiError(Exception): 5 | def __init__(self, *args, **kwargs): 6 | self.reason = kwargs.get("reason") 7 | super(ApiError, self).__init__(*args) 8 | 9 | 10 | class ApiNotFoundError(ApiError): 11 | pass 12 | 13 | 14 | class ApiPermissionError(ApiError): 15 | pass 16 | 17 | 18 | class ApiValueError(ApiError): 19 | pass 20 | 21 | 22 | class ApiConflictError(ApiError): 23 | pass 24 | -------------------------------------------------------------------------------- /examples/bamboo/bamboo-delete-build-result.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bamboo 3 | import os 4 | 5 | BAMBOO_URL = os.environ.get('BAMBOO_URL', 'http://localhost:8085') 6 | ATLASSIAN_USER = os.environ.get('ATLASSIAN_USER', 'admin') 7 | ATLASSIAN_PASSWORD = os.environ.get('ATLASSIAN_PASSWORD', 'admin') 8 | 9 | bamboo = Bamboo( 10 | url=BAMBOO_URL, 11 | username=ATLASSIAN_USER, 12 | password=ATLASSIAN_PASSWORD) 13 | 14 | bamboo.delete_build_result('PLAN-TODELETE-7') 15 | -------------------------------------------------------------------------------- /examples/confluence/confluence-page-get.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | 4 | confluence = Confluence( 5 | url='http://localhost:8090', 6 | username='admin', 7 | password='admin') 8 | 9 | # If you know Space and Title 10 | content1 = confluence.get_page_by_title(space='SPACE', 11 | title='page title') 12 | 13 | print(content1) 14 | 15 | # If you know page_id of the page 16 | content2 = confluence.get_page_by_id(page_id=1123123123) 17 | 18 | print(content2) 19 | -------------------------------------------------------------------------------- /examples/bamboo/bamboo-add-users.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bamboo 3 | import os 4 | 5 | BAMBOO_URL = os.environ.get('BAMBOO_URL', 'http://localhost:8085') 6 | ATLASSIAN_USER = os.environ.get('ATLASSIAN_USER', 'admin') 7 | ATLASSIAN_PASSWORD = os.environ.get('ATLASSIAN_PASSWORD', 'admin') 8 | 9 | bamboo = Bamboo( 10 | url=BAMBOO_URL, 11 | username=ATLASSIAN_USER, 12 | password=ATLASSIAN_PASSWORD) 13 | 14 | 15 | bamboo.add_users_into_group(group_name='group_name', users=['user_name']) 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/bamboo/bamboo-plan-directory-info.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bamboo 3 | import os 4 | 5 | BAMBOO_URL = os.environ.get('BAMBOO_URL', 'http://localhost:8085') 6 | ATLASSIAN_USER = os.environ.get('ATLASSIAN_USER', 'admin') 7 | ATLASSIAN_PASSWORD = os.environ.get('ATLASSIAN_PASSWORD', 'admin') 8 | 9 | bamboo = Bamboo( 10 | url=BAMBOO_URL, 11 | username=ATLASSIAN_USER, 12 | password=ATLASSIAN_PASSWORD) 13 | 14 | plan_directories_roots = bamboo.plan_directory_info('PROJ-PLAN') 15 | 16 | print(plan_directories_roots) 17 | 18 | -------------------------------------------------------------------------------- /examples/bamboo/bamboo-delete-plan-or-result.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bamboo 3 | import os 4 | 5 | BAMBOO_URL = os.environ.get('BAMBOO_URL', 'http://localhost:8085') 6 | ATLASSIAN_USER = os.environ.get('ATLASSIAN_USER', 'admin') 7 | ATLASSIAN_PASSWORD = os.environ.get('ATLASSIAN_PASSWORD', 'admin') 8 | 9 | bamboo = Bamboo( 10 | url=BAMBOO_URL, 11 | username=ATLASSIAN_USER, 12 | password=ATLASSIAN_PASSWORD) 13 | 14 | # Delete entire plan 15 | bamboo.delete_plan('PLAN-TODELETE') 16 | 17 | # Delete single result 18 | bamboo.delete_build_result('PLAN-TODELETE-7') 19 | -------------------------------------------------------------------------------- /atlassian/__init__.py: -------------------------------------------------------------------------------- 1 | from .bamboo import Bamboo 2 | from .bitbucket import Bitbucket 3 | from .bitbucket import Bitbucket as Stash 4 | from .confluence import Confluence 5 | from .crowd import Crowd 6 | from .jira import Jira 7 | from .marketplace import MarketPlace 8 | from .portfolio import Portfolio 9 | from .service_desk import ServiceDesk 10 | from .xray import Xray 11 | 12 | __all__ = [ 13 | 'Confluence', 14 | 'Jira', 15 | 'Bitbucket', 16 | 'Portfolio', 17 | 'Bamboo', 18 | 'Stash', 19 | 'Crowd', 20 | 'ServiceDesk', 21 | 'MarketPlace', 22 | 'Xray' 23 | ] 24 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples and apps 2 | 3 | Here you can find different examples of how to use a library. 4 | 5 | You can use it as a reference for your apps or just use it "as is" for your needs. 6 | Feel free to send us new examples if you think, that we miss something important. 7 | There are simple rules each example should follow: 8 | * **Do not store** any credentials in VCS 9 | * Do not use any additional dependencies except the python build-in's 10 | * Follow the PEP-8 and format your code. 11 | 12 | JetBrains PyCharm built-in formatter is perfectly fine and used a lot at this library 13 | -------------------------------------------------------------------------------- /examples/confluence/confluence-get-space-content.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | import logging 4 | from pprint import pprint 5 | 6 | 7 | CONFLUENCE_URL = "http://conlfuence.example.com" 8 | CONFLUENCE_LOGIN = "gonchik.tsymzhitov" 9 | CONFLUENCE_PASSWORD = "************" 10 | 11 | logging.basicConfig(level=logging.DEBUG) 12 | 13 | confluence = Confluence( 14 | url=CONFLUENCE_URL, 15 | username=CONFLUENCE_LOGIN, 16 | password=CONFLUENCE_PASSWORD, 17 | timeout=180) 18 | 19 | pgs = confluence.get_space_content("SPACE") 20 | pprint(pgs["page"]["results"]) 21 | -------------------------------------------------------------------------------- /examples/confluence/confluence-get-space-permissions.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | import logging 4 | from pprint import pprint 5 | 6 | CONFLUENCE_URL = "http://conlfuence.example.com" 7 | CONFLUENCE_LOGIN = "gonchik.tsymzhitov" 8 | CONFLUENCE_PASSWORD = "************" 9 | 10 | logging.basicConfig(level=logging.DEBUG) 11 | 12 | confluence = Confluence( 13 | url=CONFLUENCE_URL, 14 | username=CONFLUENCE_LOGIN, 15 | password=CONFLUENCE_PASSWORD, 16 | timeout=180) 17 | 18 | confluence.allow_redirects = False 19 | pprint(confluence.get_space_permissions("DOC")) 20 | -------------------------------------------------------------------------------- /examples/crowd/crowd-get-user.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Crowd 3 | import os 4 | import logging 5 | 6 | log = logging.getLogger() 7 | log.setLevel(logging.DEBUG) 8 | 9 | CROWD_URL = os.environ.get('CROWD_URL', 'http://localhost:8085/crowd') 10 | CROWD_APPLICATION = os.environ.get('CROWD_APPLICATION', 'bamboo') 11 | CROWD_APPLICATION_PASSWORD = os.environ.get('CROWD_APPLICATION_PASSWORD', 'admin') 12 | 13 | crowd = Crowd( 14 | url=CROWD_URL, 15 | username=CROWD_APPLICATION, 16 | password=CROWD_APPLICATION_PASSWORD) 17 | 18 | user_details = crowd.user('xdfjklm') 19 | print(user_details) 20 | -------------------------------------------------------------------------------- /examples/bamboo/bamboo-activity.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bamboo 3 | import os 4 | 5 | """ 6 | Example for check exist activity and agent status 7 | """ 8 | BAMBOO_URL = os.environ.get('BAMBOO_URL', 'http://localhost:8085') 9 | ATLASSIAN_USER = os.environ.get('ATLASSIAN_USER', 'admin') 10 | ATLASSIAN_PASSWORD = os.environ.get('ATLASSIAN_PASSWORD', 'admin') 11 | 12 | bamboo = Bamboo( 13 | url=BAMBOO_URL, 14 | username=ATLASSIAN_USER, 15 | password=ATLASSIAN_PASSWORD) 16 | 17 | agent_status = bamboo.agent_status() 18 | print(agent_status) 19 | 20 | activity = bamboo.activity() 21 | print(activity) 22 | -------------------------------------------------------------------------------- /examples/jira/jira-tempo-timesheet-worklogs.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | from pprint import pprint 4 | import logging 5 | 6 | jira = Jira( 7 | url='http://localhost:8080', 8 | username='admin', 9 | password='admin', 10 | # You can use it without advanced mode. 11 | advanced_mode=True 12 | ) 13 | 14 | logging.basicConfig(level=logging.DEBUG) 15 | # deprecated_issue_worklog = jira.tempo_timesheets_get_worklogs_by_issue("PROJ-1234") 16 | latest_issue_worklog = jira.tempo_4_timesheets_find_worklogs(taskKey=["PROJ-1234"]) 17 | # pprint(deprecated_issue_worklog.json()) 18 | pprint(latest_issue_worklog.json()) 19 | -------------------------------------------------------------------------------- /examples/confluence/confluence-nested-error.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | from atlassian.confluence import ApiError 4 | 5 | """ This example shows a way to get the real reason for an exception""" 6 | try: 7 | confluence = Confluence( 8 | url='http://some_site_without_permission.com', 9 | username='admin', 10 | password='admin') 11 | result = confluence.get_user_details_by_username(username="gonchik.tsymzhitov", expand="status") 12 | except ApiError as e: 13 | print("FAILURE: {}, caused by {}".format(e, e.reason if e.reason is not None else "unknown reason")) 14 | else: 15 | print(result) 16 | -------------------------------------------------------------------------------- /examples/crowd/crowd-get-group-nested-members.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Crowd 3 | import os 4 | import logging 5 | 6 | log = logging.getLogger() 7 | log.setLevel(logging.DEBUG) 8 | 9 | CROWD_URL = os.environ.get('CROWD_URL', 'http://localhost:8085/crowd') 10 | CROWD_APPLICATION = os.environ.get('CROWD_APPLICATION', 'bamboo') 11 | CROWD_APPLICATION_PASSWORD = os.environ.get('CROWD_APPLICATION_PASSWORD', 'admin') 12 | 13 | crowd = Crowd( 14 | url=CROWD_URL, 15 | username=CROWD_APPLICATION, 16 | password=CROWD_APPLICATION_PASSWORD) 17 | 18 | group_members = crowd.group_nested_members('bamboo-user') 19 | print(group_members) 20 | -------------------------------------------------------------------------------- /examples/confluence/confluence-get-group-members.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | 4 | confluence = Confluence( 5 | url='http://localhost:8090', 6 | username='admin', 7 | password='admin') 8 | # this example related get all user from group e.g. group_name 9 | group_name = 'confluence-users' 10 | flag = True 11 | i = 0 12 | limit = 50 13 | result = [] 14 | while flag: 15 | response = confluence.get_group_members(group_name=group_name, start=i * limit, limit=limit) 16 | if response and len(response): 17 | i += 1 18 | result.append(response) 19 | else: 20 | flag = False 21 | print(result) 22 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /examples/confluence/confluence-export-page.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | 4 | """This example shows how to export pages""" 5 | 6 | confluence = Confluence( 7 | url='https://test.atlassian.net/wiki', 8 | username='admin', 9 | password='api-key', 10 | api_version='cloud' 11 | ) 12 | 13 | if __name__ == '__main__': 14 | space = 'TEST' 15 | page_title = 'Test' 16 | page_id = confluence.get_page_id(space, page_title) 17 | content = confluence.export_page(page_id) 18 | with open(page_title + ".pdf", 'wb') as pdf_file: 19 | pdf_file.write(content) 20 | pdf_file.close() 21 | print("Completed") 22 | -------------------------------------------------------------------------------- /examples/confluence/confluence-attach-file-as-link.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | from datetime import datetime 4 | import logging 5 | 6 | 7 | confluence = Confluence( 8 | url='http://localhost:8090', 9 | username='admin', 10 | password='admin', 11 | ) 12 | 13 | logging.basicConfig(level=logging.DEBUG) 14 | 15 | filename = "test_file.txt" 16 | with open(filename, "w") as f: 17 | f.write(str(datetime.utcnow())) 18 | 19 | confluence.attach_file(filename, page_id="123456789") 20 | 21 | link = """

22 | 23 | 24 | 25 |

""".format(filename) 26 | 27 | confluence.append_page(123456789, "Page Title", link) 28 | -------------------------------------------------------------------------------- /examples/confluence/confluence-export-pages.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | 4 | """This example shows how to export pages """ 5 | 6 | confluence = Confluence( 7 | url='http://localhost:8090', 8 | username='admin', 9 | password='admin') 10 | 11 | 12 | def save_file(content, title): 13 | file_pdf = open(title + ".pdf", 'w') 14 | file_pdf.write(content) 15 | file_pdf.close() 16 | print("Completed") 17 | 18 | 19 | if __name__ == '__main__': 20 | label = "super-important" 21 | pages = confluence.get_all_pages_by_label(label=label, start=0, limit=10) 22 | for page in pages: 23 | response = confluence.get_page_as_pdf(page['id']) 24 | save_file(content=response, title=page['title']) 25 | -------------------------------------------------------------------------------- /examples/bitbucket/bitbucket-project.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bitbucket 3 | 4 | 5 | def html(project): 6 | html_data = '\n' 7 | html_data += '\t\n' 8 | html_data += '\t\n'.format(**project) 9 | html_data += '\t\n'.format(**project) 10 | html_data += '\t\n'.format(**project) 11 | html_data += '\t\n'.format(**project) 12 | return html_data + '
ITEMVALUE
key{key}
name{name}
description{description}
id{id}
\n' 13 | 14 | 15 | bitbucket = Bitbucket( 16 | url='http://localhost:7990', 17 | username='admin', 18 | password='admin') 19 | 20 | data = bitbucket.project('DEMO') 21 | print(html(data)) 22 | -------------------------------------------------------------------------------- /examples/confluence/confluence-subtree-cleaner.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | 4 | """ 5 | This example shows how to clean page versions for subtree of pages 6 | """ 7 | 8 | CONFLUENCE_URL = "confluence.example.com" 9 | CONFLUENCE_LOGIN = "gonchik.tsymzhitov" 10 | CONFLUENCE_PASSWORD = "passwordpassword" 11 | 12 | if __name__ == '__main__': 13 | confluence = Confluence( 14 | url=CONFLUENCE_URL, 15 | username=CONFLUENCE_LOGIN, 16 | password=CONFLUENCE_PASSWORD, 17 | timeout=190 18 | ) 19 | remained_count = 1 20 | 21 | subtree = confluence.get_subtree_of_content_ids('123123') 22 | for page_id in subtree: 23 | confluence.remove_page_history_keep_version(page_id=page_id, keep_last_versions=remained_count) 24 | -------------------------------------------------------------------------------- /examples/jira/jira-project-security-level-checker.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import logging 3 | from atlassian import Jira 4 | from pprint import pprint 5 | 6 | """ How to get server info with health check""" 7 | 8 | jira = Jira( 9 | url="https://jira.example.com/", 10 | username='gonchik.tsymzhitov', 11 | password='********') 12 | 13 | log = logging.getLogger("com.gonchik.python.scripts.example") 14 | logging.basicConfig(level=logging.ERROR) 15 | 16 | projects = jira.get_all_projects() 17 | for project in projects: 18 | project_key = project.get("key") 19 | try: 20 | value = jira.get_project_issue_security_scheme(project_key).get("name") or "None" 21 | except Exception as e: 22 | log.error(e) 23 | value = "None" 24 | print(project_key + " has issue security scheme " + value) 25 | -------------------------------------------------------------------------------- /examples/bamboo/bamboo-add-plan-branch.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bamboo 3 | import argparse 4 | 5 | """ 6 | How to create the plan branch 7 | """ 8 | bamboo = Bamboo(url="https://", username="", password="") 9 | 10 | 11 | def create_plan_branch(plan, vcs_branch): 12 | bamboo_branch = vcs_branch.replace("/", "-") 13 | return bamboo.create_branch( 14 | plan, bamboo_branch, vcs_branch=vcs_branch, enabled=True 15 | ) 16 | 17 | 18 | def main(): 19 | parser = argparse.ArgumentParser() 20 | parser.add_argument("--plan") 21 | parser.add_argument("--vcs_branch") 22 | args = parser.parse_args() 23 | 24 | branch = create_plan_branch(plan=args.plan, vcs_branch=args.vcs_branch) 25 | print(branch.get("key") or branch) 26 | 27 | 28 | if __name__ == "__main__": 29 | main() 30 | -------------------------------------------------------------------------------- /examples/jira/jira-project-administrators.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | 4 | jira = Jira( 5 | url='http://localhost:8080', 6 | username='admin', 7 | password='admin') 8 | 9 | html = """ 10 | 11 | 12 | 13 | 14 | 15 | """ 16 | 17 | for data in jira.project_leaders(): 18 | html += """ 19 | 20 | 21 | 22 | 23 | """.format(**data) 24 | 25 | html += '
Project KeyProject NameLeaderEmail
{project_key}{project_name}{lead_name}{lead_email}
' 26 | 27 | print(html) 28 | -------------------------------------------------------------------------------- /examples/jira/jira-sprint-create.py: -------------------------------------------------------------------------------- 1 | from atlassian import Jira 2 | 3 | sprint_name = 'Sprint 2' 4 | origin_board_id = 1000 5 | start_datetime = '2021-05-30T14:42:23.643068' 6 | end_datetime = '2021-06-30T14:42:23.643068' 7 | goal = "And we're out of beta, we're releasing on time." 8 | 9 | 10 | jira = Jira( 11 | url='http://localhost:8080', 12 | username='admin', 13 | password='admin') 14 | 15 | # name and board_id are mandatory only 16 | # Necessary for user to have `Manage Sprint` permission for the board 17 | resp = jira.create_sprint( 18 | name=sprint_name, 19 | board_id=origin_board_id, 20 | start_date=start_datetime, 21 | end_date=end_datetime, 22 | goal=goal 23 | ) 24 | -------------------------------------------------------------------------------- /examples/jira/jira-copy-components-from-A-to-B.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | 4 | jira = Jira( 5 | url="http://localhost:8080/", 6 | username="jira-administrator", 7 | password="admin") 8 | 9 | """That example show how to copy components from one project into another""" 10 | 11 | DST_PROJECT = "PROJECT_B" 12 | SRC_PROJECT = "PROJECT_A" 13 | components = jira.get_project_components(SRC_PROJECT) 14 | 15 | for component in components: 16 | data = {"project": DST_PROJECT, 17 | "description": component.get('description'), 18 | "leadUserName": component.get('leadUserName'), 19 | "name": component.get('name'), 20 | "assigneeType": component.get('assigneeType') 21 | } 22 | jira.create_component(data) 23 | print("{} - component created ".format(component.get('name'))) 24 | -------------------------------------------------------------------------------- /examples/jira/jira-custom-field-value-swaper.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | 4 | # This example shoes how to copy custom field value from on issue to another 5 | # define custom field id, in notation customfield_id 6 | 7 | custom_field_id = 'customfield_102589' 8 | source_issue_key = "TST-1" 9 | destination_issue_key = "TST-2" 10 | 11 | jira = Jira( 12 | url='http://localhost:8080', 13 | username='admin', 14 | password='admin') 15 | 16 | # get value and save 17 | customfield_value = jira.issue_field_value(source_issue_key, custom_field_id) 18 | # prepare data like this https://developer.atlassian.com/server/jira/platform/jira-rest-api-example-edit-issues-6291632 19 | custom_field_preparation = {custom_field_id: customfield_value} 20 | # update custom field on destination issue 21 | jira.update_issue_field(destination_issue_key, custom_field_preparation) 22 | -------------------------------------------------------------------------------- /examples/jira/jira-add-components-to-all-projects.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | 4 | jira = Jira( 5 | url="http://localhost:8080/", 6 | username="jira-administrator", 7 | password="admin") 8 | 9 | components = ["Data Base", "HTML", "JavaScript"] 10 | 11 | """That example show how to create components on all existing projects, only skipping the one in a provided list""" 12 | 13 | project_to_skip = ["SI", "SA", "ETA"] 14 | 15 | for i in jira.get_all_projects(included_archived=None): 16 | if i["key"] in project_to_skip: 17 | print("Skipping project {} ".format(i["key"])) 18 | else: 19 | for j in components: 20 | print("Creating in project {} ".format(i["key"])) 21 | comp = {"project": i["key"], "name": j} 22 | jira.create_component(comp) 23 | print("{} - component created ".format(comp.get('name'))) 24 | -------------------------------------------------------------------------------- /examples/bitbucket/bitbucket-pullrequest-tasks.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bitbucket 3 | 4 | bitbucket = Bitbucket( 5 | url='http://localhost:7990', 6 | username='admin', 7 | password='admin') 8 | 9 | # Get all tasks for a pull-request by pull_request_id 10 | data = bitbucket.get_tasks("project_name", "repository_name", "pull_request_id") 11 | print(data) 12 | 13 | # Get information about task by task_id 14 | data = bitbucket.get_task("task_id") 15 | print(data) 16 | 17 | # Add task to the comment by comment_ID in pull-request 18 | data = bitbucket.add_task("comment_ID", "task_text") 19 | print(data) 20 | 21 | # Update task by task_ID with new state (OPEN, RESOLVED) or/and text. 22 | data = bitbucket.update_task("task_ID", text="text", state="OPEN") 23 | print(data) 24 | 25 | # Delete task by task_ID. RESOLVED tasks can't be deleted 26 | data = bitbucket.delete_task("task_ID") 27 | print(data) 28 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /examples/bitbucket/bitbucket-projects-administrators.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import logging 3 | from atlassian import Bitbucket 4 | 5 | 6 | logging.basicConfig(level=logging.DEBUG, format='[%(asctime).19s] [%(levelname)s] %(message)s') 7 | logging.getLogger('requests').setLevel(logging.WARNING) 8 | log = logging.getLogger('bitbucket-projects-administrators') 9 | 10 | bitbucket = Bitbucket( 11 | url='http://localhost:7990', 12 | username='admin', 13 | password='admin') 14 | 15 | html = '' 16 | 17 | for data in bitbucket.all_project_administrators(): 18 | html += '' 22 | 23 | html += '
Project KeyProject NameAdministrator
{project_key}{project_name}
    '.format(**data) 19 | for user in data['project_administrators']: 20 | html += '
  • {name}
  • '.format(**user) 21 | html += '

' 24 | 25 | print(html) 26 | -------------------------------------------------------------------------------- /examples/bitbucket/bitbucket-tag-manipulations.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bitbucket 3 | 4 | """This example shows how to create, get and delete tags""" 5 | 6 | bitbucket = Bitbucket( 7 | url='http://localhost:7990', 8 | username='admin', 9 | password='admin') 10 | 11 | if __name__ == '__main__': 12 | response = bitbucket.set_tag(project='gonchik.tsymzhitov', 13 | repository='gonchik', 14 | tag_name='test1', 15 | commit_revision='ebcf5fdffa0', 16 | description='Stable release') 17 | print("Response after set_tag method") 18 | print(response) 19 | 20 | response = bitbucket.get_project_tags(project='INT', repository='jira-plugins', tag_name='test1') 21 | print("Retrieve tag") 22 | print(response) 23 | print("Remove tag") 24 | bitbucket.delete_tag(project='INT', repository='jira-plugins', tag_name='test1') 25 | -------------------------------------------------------------------------------- /examples/confluence/confluence-page-properties.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | 4 | """This example shows how to export pages""" 5 | 6 | confluence = Confluence( 7 | url='http://localhost:8090', 8 | username='admin', 9 | password='admin') 10 | 11 | # Set page property 12 | data = { 13 | "key": "newprp", 14 | "value": { 15 | "anything": "goes" 16 | } 17 | } 18 | print("SET") 19 | print(confluence.set_page_property(242793586, data)) 20 | 21 | # # Get page property 22 | print("GET") 23 | print(confluence.get_page_property(242793586, "newprp")) 24 | 25 | 26 | # Update page property 27 | data = { 28 | "key": "newprp", 29 | "value": { 30 | "anything": "goes around" 31 | }, 32 | "version": { 33 | "number": 2, 34 | "minorEdit": False, 35 | "hidden": False 36 | } 37 | } 38 | print("UPDATE") 39 | print(confluence.update_page_property(242793586, data)) 40 | 41 | # Delete page property 42 | print("DELETE") 43 | print(confluence.delete_page_property(242793586, "newprp")) 44 | -------------------------------------------------------------------------------- /examples/bitbucket/bitbucket-manage-pull-request.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bitbucket 3 | 4 | bitbucket = Bitbucket( 5 | url='http://localhost:7990', 6 | username='admin', 7 | password='admin') 8 | pr_id = 12345 9 | 10 | pr = bitbucket.get_pullrequest("project_name", "repository_name", pr_id) 11 | ver = pr.json().get("version") 12 | print("PR version: {}".format(ver)) 13 | 14 | response = bitbucket.decline_pull_request("project_name", "repository_name", pr_id, ver) 15 | print("Declined: {}".format(response)) 16 | ver = response.json().get("version") 17 | print("PR version: {}".format(ver)) 18 | 19 | response = bitbucket.reopen_pull_request("project_name", "repository_name", pr_id, ver) 20 | print("Reopen: {}".format(response)) 21 | ver = response.json().get("version") 22 | print("PR version: {}".format(ver)) 23 | 24 | response = bitbucket.is_pull_request_can_be_merged("project_name", "repository_name", pr_id) 25 | print("Reopen: {}".format(response)) 26 | print("PR version: {}".format(ver)) 27 | 28 | response = bitbucket.merge_pull_request("project_name", "repository_name", pr_id, ver) 29 | print("Merged: {}".format(response)) 30 | -------------------------------------------------------------------------------- /examples/bamboo/bamboo-first-steps.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bamboo 3 | import os 4 | 5 | BAMBOO_URL = os.environ.get('BAMBOO_URL', 'http://localhost:8085') 6 | ATLASSIAN_USER = os.environ.get('ATLASSIAN_USER', 'admin') 7 | ATLASSIAN_PASSWORD = os.environ.get('ATLASSIAN_PASSWORD', 'admin') 8 | 9 | bamboo = Bamboo( 10 | url=BAMBOO_URL, 11 | username=ATLASSIAN_USER, 12 | password=ATLASSIAN_PASSWORD) 13 | 14 | 15 | # Methods in plural (projects, plans, results...) return a generator that iterates through 16 | # all results without the need of dealing need with pagination 17 | 18 | # for project in bamboo.projects(): 19 | # print(project) 20 | 21 | 22 | for branch in bamboo.plan_branches('PROJ-SP2'): 23 | print(branch) 24 | 25 | # for result in bamboo.latest_results(): 26 | # print(result) 27 | 28 | # for result in bamboo.plan_results(project_key='FOO', plan_key='BAR'): 29 | # print(result) 30 | 31 | # for report in bamboo.reports(): 32 | # print(report) 33 | 34 | 35 | # Methods in singular (project, plan, result...) return a single dictionary 36 | 37 | print(bamboo.project('FOO')) 38 | 39 | print(bamboo.build_result('FOO-BAR-1')) 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /atlassian/request_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from six import PY3 4 | 5 | 6 | # Additional log methods 7 | def logger_has_handlers(logger): 8 | """Since Python 2 doesn't provide Logger.hasHandlers(), we have to 9 | perform the lookup by ourself.""" 10 | 11 | if PY3: 12 | return logger.hasHandlers() 13 | else: 14 | c = logger 15 | rv = False 16 | while c: 17 | if c.handlers: 18 | rv = True 19 | break 20 | if not c.propagate: 21 | break 22 | else: 23 | c = c.parent 24 | return rv 25 | 26 | 27 | def get_default_logger(name): 28 | """Get a logger from default logging manager. If no handler 29 | is associated, add a default NullHandler""" 30 | 31 | logger = logging.getLogger(name) 32 | if not logger_has_handlers(logger): 33 | # If logging is not configured in the current project, configure 34 | # this logger to discard all logs messages. This will prevent 35 | # the 'No handlers could be found for logger XXX' error on Python 2, 36 | # and avoid redirecting errors to the default 'lastResort' 37 | # StreamHandler on Python 3 38 | logger.addHandler(logging.NullHandler()) 39 | return logger 40 | -------------------------------------------------------------------------------- /examples/confluence/confluence-search-cql.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | 4 | """This example shows how to use the cql 5 | More detail documentation located here https://developer.atlassian.com/server/confluence/advanced-searching-using-cql 6 | """ 7 | 8 | confluence = Confluence( 9 | url='http://localhost:8090', 10 | username='admin', 11 | password='admin') 12 | 13 | WORD = "componentname" 14 | 15 | 16 | def search_word(word): 17 | """ 18 | Get all found pages with order by created date 19 | :param word: 20 | :return: json answer 21 | """ 22 | cql = "siteSearch ~ {} order by created".format(word) 23 | answers = confluence.cql(cql) 24 | for answer in answers.get('results'): 25 | print(answer) 26 | 27 | 28 | def search_word_in_space(space, word): 29 | """ 30 | Get all found pages with order by created date 31 | :param space 32 | :param word: 33 | :return: json answer 34 | """ 35 | cql = "space.key={} and (text ~ {})".format(space, word) 36 | answers = confluence.cql(cql, expand='space,body.view') 37 | for answer in answers.get('results'): 38 | print(answer) 39 | 40 | 41 | if __name__ == '__main__': 42 | search_word(word=WORD) 43 | search_word_in_space(space="TST", word=WORD) 44 | -------------------------------------------------------------------------------- /examples/jira/jira-convert-group-members-into-user-in-role.py: -------------------------------------------------------------------------------- 1 | from atlassian import Jira 2 | import logging 3 | 4 | logging.basicConfig(level=logging.ERROR) 5 | 6 | jira = Jira( 7 | url='http://localhost:8080', 8 | username='admin', 9 | password='admin') 10 | 11 | """That example show how to copy group members into role members""" 12 | 13 | 14 | def convert_group_into_users_in_role(project_key, role_id, group_name): 15 | users = jira.get_all_users_from_group(group=group_name, limit=1000).get("values") 16 | for user in users: 17 | jira.add_user_into_project_role(project_key=project_key, role_id=role_id, user_name=user.get("name")) 18 | print(f"{user.get('name')} added into role_id {role_id} in {project_key}") 19 | 20 | 21 | group_name_to_find = "old-developers" 22 | roles = jira.get_all_global_project_roles() 23 | projects = jira.get_all_projects(included_archived=True) 24 | for project in projects: 25 | for role in roles: 26 | members_of_role = jira.get_project_actors_for_role_project(project.get("key"), role.get("id")) 27 | if not members_of_role: 28 | continue 29 | for member in members_of_role: 30 | if member.get("type") == "atlassian-group-role-actor": 31 | if member.get("name") == group_name_to_find: 32 | print(f'{project.get("key")} has {role.get("name")}') 33 | convert_group_into_users_in_role(project.get("key"), role.get("id"), group_name_to_find) 34 | -------------------------------------------------------------------------------- /examples/jira/jira-admins-confluence-page.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import logging 3 | from atlassian import Confluence 4 | from atlassian import Jira 5 | 6 | logging.basicConfig(level=logging.DEBUG, format='[%(asctime).19s] [%(levelname)s] %(message)s') 7 | logging.getLogger('requests').setLevel(logging.WARNING) 8 | log = logging.getLogger('jira-projects-administrators') 9 | 10 | jira = Jira( 11 | url='http://localhost:8080', 12 | username='admin', 13 | password='admin') 14 | 15 | confluence = Confluence( 16 | url='http://localhost:8090', 17 | username='admin', 18 | password='admin') 19 | 20 | html = [""" 21 | 22 | 23 | 24 | 25 | 26 | """] 27 | 28 | 29 | for data in jira.project_leaders(): 30 | log.info('{project_key} leader is {lead_name} <{lead_email}>'.format(**data)) 31 | row = """ 32 | 33 | 34 | 35 | 36 | """ 37 | html.append(row.format(**data)) 38 | 39 | html.append('
Project KeyProject NameLeaderEmail
{project_key}{project_name}{lead_name}{lead_email}

') 40 | 41 | status = confluence.create_page( 42 | space='DEMO', 43 | parent_id=confluence.get_page_id('DEMO', 'demo'), 44 | title='Jira Administrators', 45 | body='\r\n'.join(html)) 46 | 47 | print(status) 48 | -------------------------------------------------------------------------------- /examples/confluence/confluence-check-unknown-attachment-error.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | 4 | """This example how to detect unknown-attachments errors""" 5 | 6 | confluence = Confluence( 7 | url='http://localhost:8090', 8 | username='admin', 9 | password='admin', 10 | timeout=185) 11 | 12 | 13 | def get_all_pages_ids(space_key): 14 | page_ids = [] 15 | 16 | limit = 50 17 | flag = True 18 | step = 0 19 | while flag: 20 | values = confluence.get_all_pages_from_space(space=space_key, start=step * limit, limit=limit) 21 | step += 1 22 | 23 | if len(values) == 0: 24 | flag = False 25 | print("Extracted all pages excluding restricts") 26 | else: 27 | for value in values: 28 | page_ids.append(value.get('id')) 29 | 30 | return page_ids 31 | 32 | 33 | def check_unknown_attachment_in_space(space_key): 34 | """ 35 | Detect errors in space 36 | :param space_key: 37 | :return: 38 | """ 39 | page_ids = get_all_pages_ids(space_key) 40 | print("Start review pages {} in {}".format(len(page_ids), space_key)) 41 | for page_id in page_ids: 42 | link = confluence.has_unknown_attachment_error(page_id) 43 | if len(link) > 0: 44 | print(link) 45 | 46 | 47 | if __name__ == '__main__': 48 | space_list = confluence.get_all_spaces() 49 | for space in space_list: 50 | print("Start review {} space".format(space['key'])) 51 | check_unknown_attachment_in_space(space['key']) 52 | -------------------------------------------------------------------------------- /examples/bitbucket/stash-user-auth-report.py: -------------------------------------------------------------------------------- 1 | from atlassian import Bitbucket 2 | from pprint import pprint 3 | from datetime import datetime 4 | import argparse 5 | import logging 6 | 7 | """ 8 | That example shows how to make a report of bitbucket usage 9 | """ 10 | 11 | stash = Bitbucket( 12 | url="https://stash.example.com", 13 | username="admin", 14 | password="*************", 15 | timeout=60) 16 | 17 | 18 | def report(limit=200, include_in_active=False): 19 | response = stash.get_users_info(stash, limit=limit) 20 | users = [] 21 | if response: 22 | users = response.get("values") or [] 23 | print("|Status|Display name| Email |Last Auth DateTime|") 24 | for user in users: 25 | auth_date = user.get('lastAuthenticationTimestamp') or None 26 | if auth_date: 27 | auth_date = int(auth_date / 1000) 28 | full_date = datetime.utcfromtimestamp(auth_date).strftime('%Y-%m-%d %H:%M:%S') 29 | else: 30 | full_date = None 31 | if include_in_active or user.get('active'): 32 | output = f"|{user.get('active')}|{user.get('displayName')}|{user.get('emailAddress')}|{full_date}|" 33 | print(output) 34 | 35 | 36 | if __name__ == '__main__': 37 | """ 38 | This part of code only executes if we run this module directly. 39 | You can still import the execute_build function and use it separately in the different module. 40 | """ 41 | # Setting the logging level. INFO|ERROR|DEBUG are the most common. 42 | logging.basicConfig(level=logging.ERROR) 43 | report(limit=1000) 44 | -------------------------------------------------------------------------------- /atlassian/marketplace.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import logging 3 | 4 | from .rest_client import AtlassianRestAPI 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | 9 | class MarketPlace(AtlassianRestAPI): 10 | """Marketplace API wrapper. 11 | """ 12 | 13 | def get_plugins_info(self, limit=10, offset=10): 14 | """ 15 | Provide plugins info 16 | :param limit: 17 | :param offset: 18 | :return: 19 | """ 20 | params = {} 21 | if offset: 22 | params['offset'] = offset 23 | if limit: 24 | params['limit'] = limit 25 | url = 'rest/1.0/plugins' 26 | return (self.get(url, params=params) or {}).get('plugins') 27 | 28 | def get_vendors_info(self, limit=10, offset=10): 29 | """ 30 | Provide vendors info 31 | :param limit: 32 | :param offset: 33 | :return: 34 | """ 35 | params = {} 36 | if offset: 37 | params['offset'] = offset 38 | if limit: 39 | params['limit'] = limit 40 | url = 'rest/1.0/vendors' 41 | return (self.get(url, params=params) or {}).get('vendors') 42 | 43 | def get_application_info(self, limit=10, offset=10): 44 | """ 45 | Information about applications 46 | :param limit: 47 | :param offset: 48 | :return: 49 | """ 50 | params = {} 51 | if offset: 52 | params['offset'] = offset 53 | if limit: 54 | params['limit'] = limit 55 | url = 'rest/2/applications' 56 | return self.get(url, params=params) 57 | -------------------------------------------------------------------------------- /examples/jira/jira-project-leaders.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from urllib.parse import quote 3 | from atlassian import Jira 4 | 5 | jira = Jira( 6 | url='http://localhost:8080', 7 | username='admin', 8 | password='admin') 9 | 10 | EMAIL_SUBJECT = quote('Jira access to project {project_key}') 11 | EMAIL_BODY = quote("""I am asking for access to the {project_key} project in Jira. 12 | 13 | To give me the appropriate permissions, assign me to a role on the page: 14 | http://localhost:8080/plugins/servlet/project-config/{project_key}/roles 15 | 16 | Role: 17 | Users - read-only access + commenting 18 | Developers - work on tasks, editing, etc. 19 | Admin - Change of configuration and the possibility of starting sprints""") 20 | 21 | MAILTO = '{lead_name}' 22 | 23 | print('|| Project Key || Project Name || Ask for Access ||') 24 | 25 | for project in jira.project_leaders(): 26 | print('| {project_key} | {project_name} | {lead_name} <{lead_email}> |'.format(project_key=project['project_key'], 27 | project_name=project['project_name'], 28 | email_subject=EMAIL_SUBJECT, 29 | email_body=EMAIL_BODY, 30 | lead_name=project['lead_name'], 31 | lead_email=project['lead_email'])) 32 | -------------------------------------------------------------------------------- /.travis/bump_version: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ ( -z ${1+x} ) || ( -z ${2+x} ) ]]; then 4 | cat << EOF 5 | usage: $(basename ${0}) repo increment 6 | 7 | Quick and dirty version bump script 8 | If a variable "BUILD_VERSION" is set, returns that variable as is 9 | If "BUILD_VERSION" is not set, returns the bumped version based on last existing tag 10 | and appends .devN where N is the build number. 11 | (tags get sorted using 'sort -V', nothing fancy here) 12 | If no tags are present, the initial version will be 0.1.0 13 | 14 | Expects arguments: 15 | - repo: the relative/absolute path to the repository 16 | - increment: (major|minor|patch) part of the version to increase, default is patch 17 | EOF 18 | exit 1 19 | fi 20 | 21 | repo_path=${1} 22 | increase=${2:patch} 23 | 24 | 25 | function autoversion(){ 26 | 27 | if [ -n "${TRAVIS_BUILD_NUMBER+x}" ]; then 28 | BUILD_NUMBER="${TRAVIS_BUILD_NUMBER}" 29 | else 30 | BUILD_NUMBER="0" # In the developer machine, this will build x.y.z.dev0 31 | fi 32 | 33 | cd ${repo_path} 34 | 35 | git fetch --tags 2>/dev/null 36 | last_tag=$(git tag | sort -Vr | head -1) 37 | 38 | # Catch existing no tags case 39 | if [ -z "${last_tag}" ]; then 40 | echo "0.1.0.dev${BUILD_NUMBER}" 41 | else 42 | a=( ${last_tag//./ } ) # replace points, split into array 43 | case ${increase} in 44 | patch) 45 | ((a[2]++)) 46 | ;; 47 | minor) 48 | ((a[1]++)) 49 | a[2]=0 50 | ;; 51 | major) 52 | ((a[0]++)) 53 | a[1]=0 54 | a[2]=0 55 | ;; 56 | esac 57 | echo "${a[0]}.${a[1]}.${a[2]}.dev${BUILD_NUMBER}" 58 | fi 59 | } 60 | 61 | if [ -n "${BUILD_VERSION+x}" ]; then 62 | echo "${BUILD_VERSION}" 63 | else 64 | autoversion 65 | fi 66 | -------------------------------------------------------------------------------- /examples/jira/jira-notification_schemes_duplicates.py: -------------------------------------------------------------------------------- 1 | from atlassian import Jira 2 | 3 | jira = Jira( 4 | url='http://localhost:8090', 5 | username='admin', 6 | password='admin') 7 | 8 | 9 | def compare_dicts(dict1, dict2): 10 | count = 0 11 | hint = [] 12 | if len(dict1) != len(dict2) and len(dict1) != len(dict2) + 1 and len(dict2) != len(dict1) + 1: 13 | return False 14 | 15 | for key in dict1: 16 | if dict1[key] != dict2.get(key): 17 | count += 1 18 | hint.append(key) 19 | if count > 1: 20 | return False 21 | if len(dict1) != len(dict2): 22 | print('(Different size') 23 | if count == 1: 24 | print('(Different: ', hint[0]) 25 | 26 | return True 27 | 28 | 29 | notificationscheme_dict = {} 30 | all_notificationschemes_dict = {} 31 | 32 | notificationschemes_ids = jira.get_notification_schemes() 33 | names = [] 34 | 35 | for notificationschemes_id in notificationschemes_ids['values']: 36 | 37 | id = notificationschemes_id['id'] 38 | notificationschemes = jira.get_notification_scheme(id, 'all') 39 | names.append(notificationschemes['name']) 40 | notificationscheme_dict = {} 41 | 42 | for scheme in notificationschemes['notificationSchemeEvents']: 43 | notificationTypes = [] 44 | 45 | for notificationType in scheme['notifications']: 46 | notificationTypes.append(notificationType['notificationType']) 47 | notificationscheme_dict[scheme['event']['name']] = notificationTypes 48 | all_notificationschemes_dict[notificationschemes['name']] = notificationscheme_dict 49 | 50 | for i in range(len(names)): 51 | for j in range(len(names)): 52 | if names and i < j: 53 | if compare_dicts(all_notificationschemes_dict[names[i]], all_notificationschemes_dict[names[j]]): 54 | print(names[i], '/', names[j]) 55 | print('same) \n -----------------------------------------------------------------') 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .sonarlint 3 | .pytest_cache 4 | # Created by .ignore support plugin (hsz.mobi) 5 | ### Python template 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | docs/_builds 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | .hypothesis/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | .static_storage/ 61 | .media/ 62 | local_settings.py 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build 73 | Thumbs.db 74 | .DS_Store 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # SageMath parsed files 89 | *.sage.py 90 | 91 | # Environments 92 | .env 93 | .venv 94 | env/ 95 | venv/ 96 | ENV/ 97 | env.bak/ 98 | venv.bak/ 99 | 100 | # Spyder project settings 101 | .spyderproject 102 | .spyproject 103 | 104 | # Rope project settings 105 | .ropeproject 106 | 107 | # mkdocs documentation 108 | /site 109 | 110 | # mypy 111 | .mypy_cache/ 112 | 113 | # 114 | *.secret 115 | 116 | #visual studio code 117 | .vscode -------------------------------------------------------------------------------- /examples/jira/jira-find-screen-similarity.py: -------------------------------------------------------------------------------- 1 | from atlassian import Jira 2 | import logging 3 | 4 | """ 5 | That example shows how to find the same screen fields based on fields number or hash of orders. 6 | used python 3 string forms f'{variable}' 7 | """ 8 | 9 | logging.basicConfig(level=logging.ERROR) 10 | 11 | jira = Jira( 12 | url="jira.example.com", 13 | username="username", 14 | password="********", 15 | timeout=10) 16 | 17 | 18 | def extract_count(json): 19 | try: 20 | # Also convert to int since update_time will be string. When comparing 21 | # strings, "10" is smaller than "2". 22 | return int(json['available_fields_count']) 23 | except KeyError: 24 | return 0 25 | 26 | 27 | all_screens = jira.get_all_screens() 28 | screens = list() 29 | count_fields_per_screen = list() 30 | hashes = list() 31 | for screen in all_screens: 32 | screen_id = screen.get("id") 33 | available_screen_fields = jira.get_all_available_screen_fields(screen_id=screen_id) 34 | field_ids = [x.get("id") for x in available_screen_fields] 35 | number_fields = len(available_screen_fields) 36 | hash_field = hash(tuple(field_ids)) 37 | hashes.append(hash_field) 38 | screens.append({"screen_id": screen_id, 39 | "available_fields_count": number_fields, 40 | "available_fields_hash": hash_field, 41 | "available_fields": field_ids}) 42 | count_fields_per_screen.append(number_fields) 43 | print(f"Number of available screen fields {number_fields} for screen with name {screen.get('name')}") 44 | 45 | screens.sort(key=extract_count, reverse=True) 46 | flipped_fields = {} 47 | 48 | print("The same screen of fields based on the count") 49 | for x in screens: 50 | if count_fields_per_screen.count(x['available_fields_count']) > 1: 51 | print(f"Please, check {jira.url}/secure/admin/ConfigureFieldScreen.jspa?id={x['screen_id']}") 52 | 53 | print("=" * 12) 54 | print("The same field screens based on the hash") 55 | for x in screens: 56 | if hashes.count(x['available_fields_hash']) > 1: 57 | print(f"Please, check {jira.url}/secure/admin/ConfigureFieldScreen.jspa?id={x['screen_id']}") 58 | -------------------------------------------------------------------------------- /examples/bitbucket/bitbucket-branch-restrictions.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Bitbucket 3 | 4 | """ 5 | For all possible arguments and values please visit: 6 | https://docs.atlassian.com/bitbucket-server/rest/latest/bitbucket-ref-restriction-rest.html 7 | """ 8 | bitbucket = Bitbucket( 9 | url='http://localhost:7990', 10 | username='admin', 11 | password='admin', 12 | advanced_mode=True) # For more simple response handling 13 | 14 | single_permission = bitbucket.set_branches_permissions( 15 | "PROJECT_KEY", 16 | matcher_type="branch", # lowercase matcher type 17 | matcher_value="master", 18 | permission_type="no-deletes", 19 | repository="repository_name", 20 | except_users=["user1", "user2"] 21 | ) 22 | print(single_permission) 23 | pid = single_permission.json().get("id") 24 | 25 | single_permission = bitbucket.get_branch_permission("PROJECT_KEY", pid) 26 | print(single_permission) 27 | 28 | deleted_permission = bitbucket.delete_branch_permission("PROJECT_KEY", pid) 29 | print(deleted_permission) 30 | 31 | multiplpe_permissions_payload = [ 32 | { 33 | "type": "read-only", 34 | "matcher": { 35 | "id": "master", 36 | "displayId": "master", 37 | "type": { 38 | "id": "BRANCH", 39 | "name": "Branch" 40 | }, 41 | "active": True 42 | }, 43 | "users": [ 44 | "user1", 45 | ], 46 | "groups": [ 47 | 48 | ], 49 | "accessKeys": [] 50 | }, 51 | { 52 | "type": "pull-request-only", 53 | "matcher": { 54 | "id": "refs/tags/**", 55 | "displayId": "refs/tags/**", 56 | "type": { 57 | "id": "PATTERN", 58 | "name": "Pattern" 59 | }, 60 | "active": True 61 | }, 62 | "users": [ 63 | "user2" 64 | ], 65 | "groups": [ 66 | 67 | ], 68 | "accessKeys": [] 69 | } 70 | ] 71 | multiple_permissions = bitbucket.set_branches_permissions( 72 | "PROJECT_KEY", 73 | multiple_permissions=multiplpe_permissions_payload, 74 | matcher_type="branch", 75 | matcher_value="master", 76 | permission_type="no-deletes", 77 | repository="repository_name", 78 | except_users=["user1", "user2"] 79 | ) 80 | print(multiple_permissions) 81 | -------------------------------------------------------------------------------- /atlassian/crowd.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import logging 3 | 4 | from .rest_client import AtlassianRestAPI 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | 9 | class Crowd(AtlassianRestAPI): 10 | """Crowd API wrapper. 11 | Important to note that you will have to use an application credentials, 12 | not user credentials, in order to access Crowd APIs""" 13 | 14 | def __init__(self, url, username, password, timeout=60, api_root='rest', api_version='latest'): 15 | super(Crowd, self).__init__(url, username, password, timeout, api_root, api_version) 16 | 17 | def _crowd_api_url(self, api, resource): 18 | return '{server}/{api_root}/{api}/{version}/{resource}'.format( 19 | server=self.url, 20 | api_root=self.api_root, 21 | api=api, 22 | version=self.api_version, 23 | resource=resource 24 | ) 25 | 26 | def user(self, username): 27 | params = {'username': username} 28 | return self.get(self._crowd_api_url('usermanagement', 'user'), params=params) 29 | 30 | def group_nested_members(self, group): 31 | params = {'groupname': group} 32 | return self.get(self._crowd_api_url('group', 'nested'), params=params) 33 | 34 | def health_check(self): 35 | """ 36 | Get health status 37 | https://confluence.atlassian.com/jirakb/how-to-retrieve-health-check-results-using-rest-api-867195158.html 38 | :return: 39 | """ 40 | # check as Troubleshooting & Support Tools Plugin 41 | response = self.get('rest/troubleshooting/1.0/check/') 42 | if not response: 43 | # check as support tools 44 | response = self.get('rest/supportHealthCheck/1.0/check/') 45 | return response 46 | 47 | def upload_plugin(self, plugin_path): 48 | """ 49 | Provide plugin path for upload into Jira e.g. useful for auto deploy 50 | :param plugin_path: 51 | :return: 52 | """ 53 | files = { 54 | 'plugin': open(plugin_path, 'rb') 55 | } 56 | upm_token = \ 57 | self.request(method='GET', path='rest/plugins/1.0/', headers=self.no_check_headers, trailing=True).headers[ 58 | 'upm-token'] 59 | url = 'rest/plugins/1.0/?token={upm_token}'.format(upm_token=upm_token) 60 | return self.post(url, files=files, headers=self.no_check_headers) 61 | -------------------------------------------------------------------------------- /tests/test_base.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | from atlassian import Jira, Confluence, Bitbucket, Bamboo, Crowd, ServiceDesk, Xray 3 | import os 4 | 5 | BAMBOO_URL = os.environ.get('BAMBOO_URL', 'http://localhost:8085') 6 | JIRA_URL = os.environ.get('BAMBOO_URL', 'http://localhost:8080') 7 | CONFLUENCE_URL = os.environ.get('BAMBOO_URL', 'http://localhost:8090') 8 | STASH_URL = os.environ.get('BAMBOO_URL', 'http://localhost:7990') 9 | SERVICE_DESK_URL = os.environ.get('SERVICE_DESK_URL', 'http://localhost:8080') 10 | XRAY_URL = os.environ.get('XRAY_URL', 'http://localhost:8080') 11 | 12 | CROWD_URL = os.environ.get('CROWD_URL', 'http://localhost:8095/crowd') 13 | CROWD_APPLICATION = os.environ.get('CROWD_APPLICATION', 'bamboo') 14 | CROWD_APPLICATION_PASSWORD = os.environ.get('CROWD_APPLICATION_PASSWORD', 'admin') 15 | 16 | ATLASSIAN_USER = os.environ.get('ATLASSIAN_USER', 'admin') 17 | ATLASSIAN_PASSWORD = os.environ.get('ATLASSIAN_PASSWORD', 'admin') 18 | 19 | 20 | class TestBasic(object): 21 | 22 | def test_init_jira(self): 23 | jira = Jira( 24 | url=JIRA_URL, 25 | username=ATLASSIAN_USER, 26 | password=ATLASSIAN_PASSWORD 27 | ) 28 | 29 | def test_init_confluence(self): 30 | confluence = Confluence( 31 | url=CONFLUENCE_URL, 32 | username=ATLASSIAN_USER, 33 | password=ATLASSIAN_PASSWORD 34 | ) 35 | 36 | def test_init_bitbucket(self): 37 | bitbucket = Bitbucket( 38 | url=STASH_URL, 39 | username=ATLASSIAN_USER, 40 | password=ATLASSIAN_PASSWORD 41 | ) 42 | 43 | def test_init_bamboo(self): 44 | bamboo = Bamboo( 45 | url=BAMBOO_URL, 46 | username=ATLASSIAN_USER, 47 | password=ATLASSIAN_PASSWORD 48 | ) 49 | 50 | def test_init_crowd(self): 51 | crowd = Crowd( 52 | url=CROWD_URL, 53 | username=CROWD_APPLICATION, 54 | password=CROWD_APPLICATION_PASSWORD) 55 | 56 | def test_init_service_desk(self): 57 | service_desk = ServiceDesk( 58 | url=SERVICE_DESK_URL, 59 | username=ATLASSIAN_USER, 60 | password=ATLASSIAN_PASSWORD 61 | ) 62 | 63 | def test_init_xray(self): 64 | xray = Xray( 65 | url=XRAY_URL, 66 | username=ATLASSIAN_USER, 67 | password=ATLASSIAN_PASSWORD 68 | ) 69 | -------------------------------------------------------------------------------- /examples/confluence/confluence-draft-page-cleaner.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | import datetime 4 | 5 | """This example shows how to remove old draft pages (it is configure by DRAFT_DAYS variable) for all spaces""" 6 | 7 | confluence = Confluence( 8 | url='http://localhost:8090', 9 | username='admin', 10 | password='admin') 11 | 12 | 13 | def clean_draft_pages_from_space(space_key, count, date_now): 14 | """ 15 | Remove draft pages from space using datetime.now 16 | :param space_key: 17 | :param count: 18 | :param date_now: 19 | :return: int counter 20 | """ 21 | pages = confluence.get_all_draft_pages_from_space(space=space_key, start=0, limit=500) 22 | for page in pages: 23 | page_id = page['id'] 24 | draft_page = confluence.get_draft_page_by_id(page_id=page_id) 25 | last_date_string = draft_page['version']['when'] 26 | last_date = datetime.datetime.strptime(last_date_string.replace(".000", "")[:-6], "%Y-%m-%dT%H:%M:%S") 27 | if (date_now - last_date) > datetime.timedelta(days=DRAFT_DAYS): 28 | count += 1 29 | print("Removing page with page id: " + page_id) 30 | confluence.remove_page_as_draft(page_id=page_id) 31 | print("Removed page with date " + last_date_string) 32 | return count 33 | 34 | 35 | def clean_all_draft_pages_from_all_spaces(days=30): 36 | """ 37 | Remove all draft pages for all spaces older than DRAFT_DAYS 38 | :param days: int 39 | :return: 40 | """ 41 | date_now = datetime.datetime.now() 42 | count = 0 43 | limit = 50 44 | flag = True 45 | i = 0 46 | while flag: 47 | space_lists = confluence.get_all_spaces(start=i * limit, limit=limit) 48 | if space_lists and len(space_lists) != 0: 49 | i += 1 50 | for space_list in space_lists: 51 | print("Start review the space {}".format(space_list['key'])) 52 | count = clean_draft_pages_from_space(space_key=space_list['key'], count=count, 53 | date_now=date_now) 54 | else: 55 | flag = False 56 | print("Script has removed {count} draft pages older than {days} days".format(count=count, days=days)) 57 | 58 | 59 | if __name__ == '__main__': 60 | DRAFT_DAYS = 30 61 | clean_all_draft_pages_from_all_spaces(days=DRAFT_DAYS) 62 | -------------------------------------------------------------------------------- /examples/bamboo/bamboo-remove-unknown-status-build-results.py: -------------------------------------------------------------------------------- 1 | from atlassian import Bamboo 2 | import logging 3 | 4 | """ 5 | That example shows how to clean up Bamboo incomplete or Unknown build results 6 | """ 7 | 8 | logging.basicConfig(level=logging.ERROR) 9 | 10 | REMOVE = True 11 | STATUS_CLEANED_RESULTS = ['Incomplete', 'Unknown'] 12 | EXCLUDED_PROJECTS = ["EXCLUDE_PROJECT"] 13 | BAMBOO_LOGIN = "admin" 14 | BAMBOO_PASS = "password" 15 | BAMBOO_URL = "https://bamboo.example.com" 16 | 17 | 18 | def get_all_projects(): 19 | return [x['key'] for x in bamboo.projects(max_results=1000)] 20 | 21 | 22 | def get_plans_from_project(proj): 23 | return [x['key'] for x in bamboo.project_plans(proj)] 24 | 25 | 26 | def get_branches_from_plan(plan_key): 27 | return [x['id'] for x in bamboo.search_branches(plan_key, max_results=1000, start=0)] 28 | 29 | 30 | def get_results_from_branch(plan_key): 31 | return [x for x in bamboo.results(plan_key, expand='results.result', max_results=100, include_all_states=True)] 32 | 33 | 34 | def remove_build_result(build_key, status): 35 | result = bamboo.build_result(build_key=build_key) 36 | if result.get("buildState") == status: 37 | print("Removing build result - {}".format(build_key)) 38 | if REMOVE: 39 | bamboo.delete_build_result(build_key=build_key) 40 | 41 | 42 | def project_review(plans): 43 | for plan in plans: 44 | print("Inspecting {} plan".format(plan)) 45 | branches = get_branches_from_plan(plan) 46 | for branch in branches: 47 | build_results = get_results_from_branch(branch) 48 | for build in build_results: 49 | build_key = build.get('buildResultKey') or None 50 | print("Inspecting build - {}".format(build_key)) 51 | if build_key: 52 | for status in STATUS_CLEANED_RESULTS: 53 | remove_build_result(build_key=build_key, status=status) 54 | 55 | 56 | if __name__ == '__main__': 57 | bamboo = Bamboo( 58 | url=BAMBOO_URL, 59 | username=BAMBOO_LOGIN, 60 | password=BAMBOO_PASS, 61 | timeout=180) 62 | projects = get_all_projects() 63 | for project in projects: 64 | if project in EXCLUDED_PROJECTS: 65 | continue 66 | print("Inspecting project - {}".format(project)) 67 | results = [] 68 | all_plans_of_project = get_plans_from_project(project) 69 | project_review(plans=all_plans_of_project) 70 | -------------------------------------------------------------------------------- /examples/bamboo/bamboo-trigger-builds-console-app.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import argparse 3 | import logging 4 | from os import environ 5 | 6 | from atlassian import Bamboo 7 | 8 | """ 9 | To set up variables, use: 10 | export BAMBOO_URL=https://your-url BAMBOO_PASSWORD=your_pass BAMBOO_USERNAME=your_username 11 | You also can use .env files, check the awesome python-dotenv module: 12 | https://github.com/theskumar/python-dotenv 13 | """ 14 | bamboo = Bamboo( 15 | url=environ.get("BAMBOO_URL"), 16 | username=environ.get("BAMBOO_USERNAME"), 17 | password=environ.get("BAMBOO_PASSWORD"), 18 | advanced_mode=True # In this app I use an advanced_mode flag to handle responses. 19 | ) 20 | 21 | 22 | def execute_build(build_key, params): 23 | """ 24 | build_key: str 25 | params: dict 26 | """ 27 | started_build = bamboo.execute_build(build_key, **params) 28 | logging.info("Build execution status: {}".format(started_build.status_code)) 29 | if started_build.status_code == 200: 30 | logging.info("Build key: {}".format(started_build.json().get('buildResultKey'))) 31 | logging.info(started_build.json().get('link', {}).get("href")) 32 | else: 33 | logging.error("Execution failed!") 34 | logging.error(started_build.json().get("message")) 35 | 36 | 37 | if __name__ == '__main__': 38 | """ 39 | This part of code only executes if we run this module directly. 40 | You can still import the execute_build function and use it separately in the different module. 41 | """ 42 | # Setting the logging level. INFO|ERROR|DEBUG are the most common. 43 | logging.basicConfig(level=logging.INFO) 44 | # Initialize argparse module with some program name and additional information 45 | parser = argparse.ArgumentParser( 46 | prog="bamboo_trigger", 47 | usage="%(prog)s BUILD-KEY --arguments [KEY VALUE]", 48 | description="Simple execution of the bamboo plan with provided key-value arguments" 49 | ) 50 | # Adding the build key as the first argument 51 | parser.add_argument("build", type=str, help="Build key") 52 | # Adding key=value parameters after the --arguments key 53 | parser.add_argument("--arguments", nargs="*") 54 | # Getting arguments 55 | args = parser.parse_args() 56 | # Make a dictionary from the command arguments 57 | build_arguments = {args.arguments[i]: args.arguments[i + 1] for i in range(0, len(args.arguments or []), 2)} 58 | # Pass build key and arguments to the function 59 | execute_build(args.build, build_arguments) 60 | -------------------------------------------------------------------------------- /examples/bitbucket/bitbucket-check-last-auth-users.py: -------------------------------------------------------------------------------- 1 | from atlassian import Bitbucket 2 | from pprint import pprint 3 | from datetime import datetime 4 | import argparse 5 | import logging 6 | 7 | """ 8 | That example shows how to make a report of bitbucket usage 9 | """ 10 | 11 | stash = Bitbucket( 12 | url="https://stash.example.com", 13 | username="admin", 14 | password="*********", 15 | timeout=60) 16 | 17 | 18 | def report(all=False, non_auth=False, limit=20): 19 | response = stash.get_users_info(stash, limit=limit) 20 | users = [] 21 | if response: 22 | users = response.get("values") or [] 23 | for user in users: 24 | print(user) 25 | auth_date = user.get('lastAuthenticationTimestamp') or None 26 | if auth_date: 27 | auth_date = int(auth_date / 1000) 28 | full_date = datetime.utcfromtimestamp(auth_date).strftime('%Y-%m-%d %H:%M:%S') 29 | else: 30 | full_date = None 31 | if full_date: 32 | output = f"{user.get('displayName')} ({user.get('emailAddress')}) authenticated on {full_date}" 33 | if all: 34 | print(output) 35 | else: 36 | output = f"{user.get('displayName')} ({user.get('emailAddress')}) not authenticated yet" 37 | if non_auth or all: 38 | print(output) 39 | 40 | 41 | if __name__ == '__main__': 42 | """ 43 | This part of code only executes if we run this module directly. 44 | You can still import the execute_build function and use it separately in the different module. 45 | """ 46 | # Setting the logging level. INFO|ERROR|DEBUG are the most common. 47 | logging.basicConfig(level=logging.ERROR) 48 | # Initialize argparse module with some program name and additional information 49 | parser = argparse.ArgumentParser( 50 | prog="bitbucker_auth_reviewer", 51 | usage="%(prog)s", 52 | description="Simple script to make a report of authenticated or non authenticated users" 53 | ) 54 | # Adding the build key as the first argument 55 | parser.add_argument("--non-auth", help="Find non-auth users", dest='non_auth', action='store_true') 56 | parser.add_argument("--all", help="Review all users", dest='all', action='store_true') 57 | # Adding key=value parameters after the --arguments key 58 | # Getting arguments 59 | args = parser.parse_args() 60 | if args.all: 61 | report(all=True) 62 | if args.non_auth: 63 | report(non_auth=True) 64 | else: 65 | report(non_auth=False) 66 | -------------------------------------------------------------------------------- /examples/bamboo/bamboo-label-based-cleaner.py: -------------------------------------------------------------------------------- 1 | from atlassian import Bamboo 2 | from datetime import datetime 3 | from datetime import timedelta 4 | import logging 5 | 6 | """ 7 | Example shows how to clean up expired build results for specific label. 8 | Feel free to modify OLDER_DAYS and LABEL parameters. 9 | You can remove, after changing value for DRY_RUN variable 10 | """ 11 | 12 | logging.basicConfig(level=logging.ERROR) 13 | BAMBOO_LOGIN = "admin" 14 | BAMBOO_PASSWORD = "password" 15 | BAMBOO_URL = "https://bamboo.example.com" 16 | 17 | DRY_RUN = True 18 | LABEL = "cores_found" 19 | OLDER_DAYS = 60 20 | 21 | 22 | def get_all_projects(): 23 | return [x['key'] for x in bamboo.projects(max_results=10000)] 24 | 25 | 26 | def get_plans_from_project(project_key): 27 | return [x['key'] for x in bamboo.project_plans(project_key, max_results=1000)] 28 | 29 | 30 | if __name__ == '__main__': 31 | bamboo = Bamboo( 32 | url=BAMBOO_URL, 33 | username=BAMBOO_LOGIN, 34 | password=BAMBOO_PASSWORD, 35 | timeout=180) 36 | projects = get_all_projects() 37 | print("Start analyzing the {} projects".format(len(projects))) 38 | for project in projects: 39 | print("Inspecting {} project".format(project)) 40 | plans = get_plans_from_project(project) 41 | print("Start analyzing the {} plans".format(len(plans))) 42 | for plan in plans: 43 | print("Inspecting {} plan".format(plan)) 44 | build_results = [x for x in bamboo.results(plan_key=plan, label=LABEL, max_results=100, 45 | include_all_states=True)] 46 | for build in build_results: 47 | build_key = build.get('buildResultKey') or None 48 | print("Inspecting {} build".format(build_key)) 49 | build_value = bamboo.build_result(build_key) 50 | build_complete_time = build_value.get("buildCompletedTime") or None 51 | if not build_complete_time: 52 | continue 53 | datetimeObj = datetime.strptime(build_complete_time.split('+')[0] + "000", '%Y-%m-%dT%H:%M:%S.%f') 54 | if datetime.now() > datetimeObj + timedelta(days=OLDER_DAYS): 55 | print("Build is old {} as build complete date {}".format(build_key, 56 | build_complete_time.strftime("%Y-%m-%d"))) 57 | if not DRY_RUN: 58 | print("Removing {} build".format(build_key)) 59 | bamboo.delete_build_result(build_key) 60 | -------------------------------------------------------------------------------- /examples/confluence/confluence-trash-cleaner.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | 4 | """This example shows how to remove page by page from trash for all spaces""" 5 | 6 | confluence = Confluence( 7 | url='http://localhost:8090', 8 | username='admin', 9 | password='admin') 10 | 11 | 12 | def clean_pages_from_space(space_key, limit=500): 13 | """ 14 | Remove all pages from trash for related space 15 | :param limit: 16 | :param space_key: 17 | :return: 18 | """ 19 | flag = True 20 | while flag: 21 | values = confluence.get_all_pages_from_space_trash(space=space_key, start=0, limit=limit, content_type='page') 22 | if not values or len(values) == 0: 23 | flag = False 24 | print("For space {} trash is empty".format(space_key)) 25 | else: 26 | print("Found in space {} pages as trashed {}".format(space_key, len(values))) 27 | for value in values: 28 | print("Removing page with title: " + value['title']) 29 | confluence.remove_page_from_trash(value['id']) 30 | 31 | 32 | def clean_blog_posts_from_space(space_key, limit=500): 33 | """ 34 | Remove all pages from trash for related space 35 | :param limit: 36 | :param space_key: 37 | :return: 38 | """ 39 | flag = True 40 | while flag: 41 | values = confluence.get_all_pages_from_space_trash(space=space_key, start=0, limit=limit, 42 | content_type='blogpost') 43 | if values and len(values) > 0: 44 | print("Found in space {} pages as trashed {}".format(space_key, len(values))) 45 | for value in values: 46 | print("Removing page with title: " + value['title']) 47 | confluence.remove_page_from_trash(value['id']) 48 | else: 49 | flag = False 50 | print("For space {} trash is empty".format(space_key)) 51 | 52 | 53 | def clean_all_trash_pages_from_all_spaces(): 54 | """ 55 | Main function for retrieve space keys and provide space for cleaner 56 | :return: 57 | """ 58 | limit = 50 59 | flag = True 60 | i = 0 61 | while flag: 62 | space_lists = confluence.get_all_spaces(start=i * limit, limit=limit) 63 | if space_lists and len(space_lists) != 0: 64 | i += 1 65 | for space_list in space_lists: 66 | print("Start review the space with key = " + space_list['key']) 67 | clean_pages_from_space(space_key=space_list['key']) 68 | clean_blog_posts_from_space(space_key=space_list['key']) 69 | else: 70 | flag = False 71 | return 0 72 | 73 | 74 | if __name__ == '__main__': 75 | clean_all_trash_pages_from_all_spaces() 76 | -------------------------------------------------------------------------------- /examples/bamboo/bamboo-remove-old-failed-results.py: -------------------------------------------------------------------------------- 1 | from atlassian import Bamboo 2 | from datetime import datetime 3 | from datetime import timedelta 4 | import logging 5 | 6 | """ 7 | That example shows how to clean up Bamboo old failed build results 8 | """ 9 | 10 | logging.basicConfig(level=logging.ERROR) 11 | 12 | DRY_RUN = False 13 | STATUS_CLEANED_RESULTS = ["Failed"] 14 | EXCLUDED_PROJECTS = ["EXCLUDE_PROJECT"] 15 | BAMBOO_LOGIN = "admin" 16 | BAMBOO_PASS = "password" 17 | BAMBOO_URL = "https://bamboo.example.com" 18 | OLDER_DAYS = 360 19 | 20 | 21 | def get_all_projects(): 22 | return [x['key'] for x in bamboo.projects(max_results=1000)] 23 | 24 | 25 | def get_plans_from_project(proj): 26 | return [x['key'] for x in bamboo.project_plans(proj)] 27 | 28 | 29 | def get_branches_from_plan(plan_key): 30 | return [x['id'] for x in bamboo.search_branches(plan_key, max_results=1000, start=0)] 31 | 32 | 33 | def get_results_from_branch(plan_key): 34 | return [x for x in bamboo.results(plan_key, expand='results.result')] 35 | 36 | 37 | def remove_build_result(build_key, status): 38 | build_value = bamboo.build_result(build_key=build_key) 39 | build_complete_time = build_value.get("buildCompletedTime") or None 40 | if not build_complete_time: 41 | return 42 | datetime_obj = datetime.strptime(build_complete_time.split('+')[0] + "000", '%Y-%m-%dT%H:%M:%S.%f') 43 | if datetime.now() > datetime_obj + timedelta(days=OLDER_DAYS): 44 | if build_value.get("buildState") == status: 45 | print("Removing build result - {}".format(build_key)) 46 | if not DRY_RUN: 47 | bamboo.delete_build_result(build_key=build_key) 48 | 49 | 50 | def project_review(plans): 51 | for plan in plans: 52 | print("Inspecting {} plan".format(plan)) 53 | branches = get_branches_from_plan(plan) 54 | for branch in branches: 55 | build_results = get_results_from_branch(branch) 56 | for build in build_results: 57 | build_key = build.get('buildResultKey') or None 58 | print("Inspecting build - {}".format(build_key)) 59 | if build_key: 60 | for status in STATUS_CLEANED_RESULTS: 61 | remove_build_result(build_key=build_key, status=status) 62 | 63 | 64 | if __name__ == '__main__': 65 | bamboo = Bamboo( 66 | url=BAMBOO_URL, 67 | username=BAMBOO_LOGIN, 68 | password=BAMBOO_PASS, 69 | timeout=180) 70 | projects = get_all_projects() 71 | for project in projects: 72 | if project in EXCLUDED_PROJECTS: 73 | continue 74 | print("Inspecting project - {}".format(project)) 75 | results = [] 76 | all_plans_of_project = get_plans_from_project(project) 77 | project_review(plans=all_plans_of_project) 78 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import find_packages 3 | from setuptools import setup 4 | 5 | with open(os.path.join('atlassian', 'VERSION')) as file: 6 | version = file.read().strip() 7 | 8 | with open('README.rst') as file: 9 | long_description = file.read() 10 | 11 | setup( 12 | name='atlassian-python-api', 13 | description='Python Atlassian REST API Wrapper', 14 | long_description=long_description, 15 | license='Apache License 2.0', 16 | version=version, 17 | download_url='https://github.com/atlassian-api/atlassian-python-api', 18 | author='Matt Harasymczuk', 19 | author_email='matt@astrotech.io', 20 | maintainer='Gonchik Tsymzhitov', 21 | maintainer_email='gonchik.tsymzhitov@gmail.com', 22 | url='https://github.com/atlassian-api/atlassian-python-api', 23 | keywords='atlassian jira core software confluence bitbucket bamboo crowd portfolio tempo servicedesk rest api', 24 | 25 | packages=find_packages(), 26 | package_dir={'atlassian': 'atlassian'}, 27 | include_package_data=True, 28 | 29 | zip_safe=False, 30 | install_requires=[ 31 | 'requests', 32 | 'six', 33 | 'oauthlib', 34 | 'requests_oauthlib' 35 | ], 36 | extras_require={ 37 | 'kerberos': ['kerberos-sspi ; platform_system=="Windows"', 38 | 'kerberos ; platform_system!="Windows"'] 39 | }, 40 | platforms='Platform Independent', 41 | 42 | classifiers=[ 43 | 'Development Status :: 4 - Beta', 44 | 'Environment :: Web Environment', 45 | 'Environment :: Console', 46 | 'Intended Audience :: Developers', 47 | 'Intended Audience :: System Administrators', 48 | 'Intended Audience :: Information Technology', 49 | 'License :: OSI Approved :: Apache Software License', 50 | 'Natural Language :: English', 51 | 'Operating System :: OS Independent', 52 | 'Operating System :: POSIX', 53 | 'Operating System :: MacOS :: MacOS X', 54 | 'Operating System :: Microsoft :: Windows', 55 | 'Programming Language :: Python', 56 | 'Programming Language :: Python :: 2.7', 57 | 'Programming Language :: Python :: 3.2', 58 | 'Programming Language :: Python :: 3.3', 59 | 'Programming Language :: Python :: 3.4', 60 | 'Programming Language :: Python :: 3.5', 61 | 'Programming Language :: Python :: 3.6', 62 | 'Programming Language :: Python :: 3.7', 63 | 'Programming Language :: Python :: 3.8', 64 | 'Topic :: Utilities', 65 | 'Topic :: Internet', 66 | 'Topic :: Internet :: WWW/HTTP', 67 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 68 | 'Topic :: Software Development :: Libraries :: Python Modules', 69 | 'Topic :: Software Development :: Libraries :: Application Frameworks'] 70 | ) 71 | -------------------------------------------------------------------------------- /examples/confluence/confluence-copy-labels.py: -------------------------------------------------------------------------------- 1 | from atlassian import Confluence 2 | from os import environ 3 | import logging 4 | import argparse 5 | 6 | """ 7 | This example related to the syncing labels between 2 spaces 8 | """ 9 | 10 | CONFLUENCE_URL = environ.get("CONFLUENCE_URL") 11 | CONFLUENCE_LOGIN = environ.get("CONFLUENCE_LOGIN") 12 | CONFLUENCE_PASSWORD = environ.get("CONFLUENCE_PASSWORD") 13 | confluence = Confluence( 14 | url=CONFLUENCE_URL, 15 | username=CONFLUENCE_LOGIN, 16 | password=CONFLUENCE_PASSWORD, 17 | timeout=185 18 | ) 19 | 20 | 21 | def sync_labels_pages(pages, destination_space): 22 | """ 23 | Sync labels between to 2 spaces 24 | :param destination_space: 25 | :param pages: 26 | :return: 27 | """ 28 | for page in pages: 29 | page_id = page.get("id") 30 | page_title = page.get("title") 31 | labels_response = confluence.get_page_labels(page_id) 32 | if labels_response.get("size") > 0: 33 | labels = labels_response.get("results") 34 | response = confluence.get_page_by_title(destination_space, page_title) 35 | if response: 36 | destination_page_id = response.get("id") 37 | for label in labels: 38 | if label.get("prefix") == "global": 39 | label_name = label.get("name") 40 | if not DRY_RUN: 41 | confluence.set_page_label(destination_page_id, label_name) 42 | print(label_name + " copied to " + page_title) 43 | return 44 | 45 | 46 | if __name__ == '__main__': 47 | 48 | # Setting the logging level. INFO|ERROR|DEBUG are the most common. 49 | logging.basicConfig(level=logging.INFO) 50 | # Initialize argparse module with some program name and additional information 51 | parser = argparse.ArgumentParser( 52 | prog="confluence_copy_labes_between_spaces", 53 | description="Simple execution for sync labels between 2 spaces" 54 | ) 55 | parser.add_argument("--source", dest="source", default="SOURCESPACE", help="Just Source Space") 56 | parser.add_argument("--destination", dest="destination", default="DESTINATIONSPACE", help="Just Destination Space") 57 | parser.add_argument("--dry-run", dest="dry_run", action="store_true") 58 | args = parser.parse_args() 59 | SOURCE_SPACE = args.source 60 | DESTINATION_SPACE = args.destination 61 | DRY_RUN = False 62 | if args.dry_run: 63 | DRY_RUN = True 64 | limit = 50 65 | flag = True 66 | step = 0 67 | while flag: 68 | values = confluence.get_all_pages_from_space(SOURCE_SPACE, step * limit, limit=limit, 69 | status="current", 70 | content_type="page") 71 | step += 1 72 | if len(values) == 0: 73 | flag = False 74 | break 75 | sync_labels_pages(values, DESTINATION_SPACE) 76 | -------------------------------------------------------------------------------- /examples/confluence/confluence-page-versions-cleaner.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Confluence 3 | 4 | CONFLUENCE_URL = "confluence.example.com" 5 | CONFLUENCE_LOGIN = "gonchik.tsymzhitov" 6 | CONFLUENCE_PASSWORD = "passwordpassword" 7 | REMAINED_PAGE_HISTORY_COUNT = 1 8 | 9 | 10 | def page_version_remover(content_id, remained_page_numbers): 11 | response = confluence.get_content_history(content_id) 12 | if not response or not response.get('latest'): 13 | return 14 | latest_version_count = int(response.get('lastUpdated').get('number')) 15 | if len(response) > 0 and latest_version_count > remained_page_numbers: 16 | print("Number of {} latest version {}".format( 17 | confluence.url_joiner(confluence.url, "/pages/viewpage.action?pageId=" + content_id), latest_version_count)) 18 | for version_page_counter in range(1, (latest_version_count - remained_page_numbers + 1), 1): 19 | confluence.remove_content_history(content_id, 1) 20 | else: 21 | print('Number of page history smaller than remained') 22 | 23 | 24 | def get_all_page_ids_from_space(space): 25 | """ 26 | :param space: 27 | :return: 28 | """ 29 | limit = 500 30 | flag = True 31 | step = 0 32 | content_ids = [] 33 | 34 | while flag: 35 | values = confluence.get_all_pages_from_space(space=space, start=limit * step, limit=limit) 36 | step += 1 37 | if len(values) == 0: 38 | flag = False 39 | print("Did not find any pages, please, check permissions") 40 | else: 41 | for value in values: 42 | print("Retrieve page with title: " + value['title']) 43 | content_ids.append((value['id'])) 44 | print("Found in space {} pages {}".format(space, len(content_ids))) 45 | return content_ids 46 | 47 | 48 | def get_all_spaces(): 49 | limit = 50 50 | flag = True 51 | i = 0 52 | space_key_list = [] 53 | while flag: 54 | space_lists = confluence.get_all_spaces(start=i * limit, limit=limit) 55 | if space_lists and len(space_lists) != 0: 56 | i += 1 57 | for space_list in space_lists: 58 | print("Start review the space with key = " + space_list['key']) 59 | space_key_list.append(space_list['key']) 60 | else: 61 | flag = False 62 | 63 | return space_key_list 64 | 65 | 66 | def reduce_page_numbers(page_id, remained_page_history_count): 67 | page_version_remover(page_id, remained_page_history_count) 68 | return 69 | 70 | 71 | if __name__ == '__main__': 72 | confluence = Confluence( 73 | url=CONFLUENCE_URL, 74 | username=CONFLUENCE_LOGIN, 75 | password=CONFLUENCE_PASSWORD, 76 | timeout=190 77 | ) 78 | space_keys = get_all_spaces() 79 | counter = 0 80 | for space_key in space_keys: 81 | print("Starting review space with key {}".format(space_key)) 82 | page_ids = get_all_page_ids_from_space(space_key) 83 | for page_id in page_ids: 84 | reduce_page_numbers(page_id=page_id, remained_page_history_count=REMAINED_PAGE_HISTORY_COUNT) 85 | -------------------------------------------------------------------------------- /examples/jira/jira-review-groups.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | 4 | jira = Jira( 5 | url='http://localhost:8080', 6 | username='admin', 7 | password='admin') 8 | 9 | 10 | def get_all_users(group, include_inactive=True): 11 | """ 12 | Get all users for group. If there more, than 50 users in group: 13 | go through the pages and append other users to the list 14 | :param group: 15 | :param include_inactive: 16 | :return: 17 | """ 18 | start = 0 19 | users = jira.get_all_users_from_group(group, include_inactive_users=include_inactive, start=start) 20 | processed_data = {'group_name': group, 'total': users['total'], 21 | 'users': [{'name': user['name'], 'active': user['active']} for user in users['values']]} 22 | while 'nextPage' in users: 23 | start += 50 24 | users = jira.get_all_users_from_group(group, include_inactive_users=include_inactive, start=start) 25 | user_list = [{'name': user['name'], 'active': user['active']} for user in users['values']] 26 | processed_data['users'] = processed_data['users'] + user_list 27 | 28 | return processed_data 29 | 30 | 31 | def sort_users_in_group(group): 32 | """ 33 | Take group, sort users by the name and return group with sorted users 34 | """ 35 | group['users'] = [sorted_group for sorted_group in sorted(group['users'], key=lambda k: k['name'])] 36 | return group 37 | 38 | 39 | def get_groups_data(): 40 | """ 41 | Get all groups, get all users for each group and sort groups by users 42 | :return: 43 | """ 44 | groups = [group['name'] for group in jira.get_groups(limit=200)['groups']] 45 | groups_and_users = [get_all_users(group) for group in groups] 46 | groups_and_users = [sort_users_in_group(group) for group in groups_and_users] 47 | return groups_and_users 48 | 49 | 50 | def get_inactive_users(groups): 51 | """ 52 | Take group list and return groups only with inactive users 53 | :param groups: 54 | :return: 55 | """ 56 | inactive_users_list = [] 57 | for group in groups: 58 | inactive_users = {'group_name': group['group_name'], 59 | 'users': [{'name': user['name'], 60 | 'active': user['active']} for user in group['users'] if not user['active']] 61 | } 62 | inactive_users_list.append(inactive_users) 63 | 64 | return inactive_users_list 65 | 66 | 67 | def exclude_inactive_users(groups): 68 | """ 69 | Excluding inactive users from groups. 70 | :param groups: 71 | :return: 72 | """ 73 | for group in groups: 74 | for user in group['users']: 75 | print('Trying to delete {} from group {}'.format(user["name"], group["group_name"])) 76 | jira.remove_user_from_group(user['name'], group['group_name']) 77 | return True 78 | 79 | 80 | def filter_groups_by_members(groups, quantity=1): 81 | """ 82 | Take groups list and return empty groups 83 | :param groups: 84 | :param quantity: 85 | :return: 86 | """ 87 | return [x for x in groups if int(x['total']) < quantity] 88 | 89 | 90 | def find_group(groups, group_name): 91 | """ 92 | Take groups list and find group by the group name 93 | :param groups: 94 | :param group_name: 95 | :return: 96 | """ 97 | for group in groups: 98 | 99 | if group['group_name'] == group_name: 100 | 101 | return group 102 | else: 103 | return 'Group {} not in list'.format(group_name) 104 | -------------------------------------------------------------------------------- /atlassian/portfolio.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import logging 3 | 4 | from .rest_client import AtlassianRestAPI 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | 9 | class Portfolio(AtlassianRestAPI): 10 | 11 | def __init__(self, plan_id, *args, **kwargs): 12 | self.plan_id = plan_id 13 | super(Portfolio, self).__init__(*args, **kwargs) 14 | 15 | def get_epic(self, epic): 16 | key = [x.get('link', None) for x in epic.get('links', [])] 17 | estimates = self.get_estimates_dict(epic['estimates']) 18 | estimates.update(Total=sum(estimates.values())) 19 | team_id = epic.get('teamId', None) 20 | return { 21 | 'title': epic.get('title', None), 22 | 'team': self.get_team_name(team_id) if team_id else None, 23 | 'description': epic.get('description', None), 24 | 'issuekey': key[0] if key else None, 25 | 'estimates': estimates} 26 | 27 | def get_plan(self): 28 | url = 'rest/roadmap/1.0/plans/{0}.json'.format(self.plan_id) 29 | return self.get(url) 30 | 31 | def get_stages(self): 32 | url = 'rest/roadmap/1.0/plans/{0}/stages.json'.format(self.plan_id) 33 | return self.get(url) 34 | 35 | def get_teams(self): 36 | url = 'rest/roadmap/1.0/plans/{0}/teams.json'.format(self.plan_id) 37 | return self.get(url) 38 | 39 | def get_team_name(self, team_id): 40 | all_teams = self.get_teams()['collection'] 41 | return [team['title'] for team in all_teams if team['id'] == str(team_id)][0] 42 | 43 | def get_config(self): 44 | url = 'rest/roadmap/1.0/plans/{0}/config.json'.format(self.plan_id) 45 | return self.get(url) 46 | 47 | def get_persons(self): 48 | url = 'rest/roadmap/1.0/plans/{0}/persons.json'.format(self.plan_id) 49 | return self.get(url) 50 | 51 | def get_streams(self): 52 | url = 'rest/roadmap/1.0/plans/{0}/streams.json'.format(self.plan_id) 53 | return self.get(url) 54 | 55 | def get_releases(self): 56 | return self.get_streams() 57 | 58 | def get_themes(self): 59 | url = 'rest/roadmap/1.0/plans/{0}/themes.json'.format(self.plan_id) 60 | return self.get(url) 61 | 62 | def get_state(self): 63 | url = 'rest/roadmap/1.0/scheduling/{0}/state.json'.format(self.plan_id) 64 | return self.get(url) 65 | 66 | def get_filter(self, limit=500): 67 | url = 'rest/roadmap/1.0/plans/{0}/workitems/filter.json'.format(self.plan_id) 68 | return self.post(url, data={'limit': limit}) 69 | 70 | def get_filters(self, query_string): 71 | url = 'rest/roadmap/1.0/system/filters.json?queryString={0}'.format(query_string) 72 | return self.get(url) 73 | 74 | def get_dependencies(self, workitem_id, plan_version): 75 | url = 'rest/roadmap/1.0/workitems/{0}/dependencies.json?planVersion={1}'.format(workitem_id, plan_version) 76 | return self.get(url) 77 | 78 | def get_stage_name(self, stage_id): 79 | all_stages = self.get_stages()['collection'] 80 | return [stage['title'] for stage in all_stages if stage['id'] == str(stage_id)][0] 81 | 82 | def get_estimates_dict(self, estimates): 83 | return {self.get_stage_name(stage['targetId']): stage['value'] for stage in estimates['stages']} 84 | 85 | def import_workitem(self, data): 86 | url = 'rest/roadmap/1.0/plans/bulk/{0}/workitems.json'.format(self.plan_id) 87 | return self.post(url, data=data) 88 | 89 | def get_jql_issues(self, jql, limit=500, exclude_linked=True, estimation_method='estimates', 90 | epic_fetch_enabled=True, load_story_points=True): 91 | url = 'rest/roadmap/1.0/system/import.json' 92 | data = {'planId': str(self.plan_id), 93 | 'query': jql, 94 | 'excludeLinked': exclude_linked, 95 | 'epicFetchEnabled': epic_fetch_enabled, 96 | 'maxResults': limit, 97 | 'estimationMethod': estimation_method, 98 | 'loadStoryPoints': load_story_points} 99 | return self.post(url, data=data)['data']['items'] 100 | -------------------------------------------------------------------------------- /examples/bitbucket/bitbucket-clean-jira-branches.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from atlassian import Jira 3 | from atlassian import Stash 4 | import logging 5 | import time 6 | 7 | """ 8 | Clean branches for closed issues 9 | """ 10 | PROJECT_KEY = 'PROJ' 11 | REPOS = ['repo1', 'repo2'] 12 | ACCEPTED_ISSUE_STATUSES = ["Closed", "Verified"] 13 | EXCLUDE_REPO_RULES = ['refs/heads/release/', 14 | 'refs/heads/master/', 15 | 'development'] 16 | LAST_COMMIT_CONDITION_IN_DAYS = 75 17 | ATLASSIAN_USER = "gonchik.tsymzhitov" 18 | ATLASSIAN_PASSWORD = "password" 19 | JIRA_URL = "http://localhost:8080" 20 | STASH_URL = "http://localhost:5999" 21 | 22 | logging.basicConfig(level=logging.ERROR) 23 | jira = Jira( 24 | url=JIRA_URL, 25 | username=ATLASSIAN_USER, 26 | password=ATLASSIAN_PASSWORD) 27 | 28 | stash = Stash( 29 | url=STASH_URL, 30 | username=ATLASSIAN_USER, 31 | password=ATLASSIAN_PASSWORD 32 | ) 33 | 34 | flag = True 35 | time_now = int(time.time()) * 1000 36 | delta_for_time_ms = LAST_COMMIT_CONDITION_IN_DAYS * 24 * 60 * 60 * 1000 37 | commit_info_key = "com.atlassian.bitbucket.server.bitbucket-branch:latest-commit-metadata" 38 | out_going_pull_request = "com.atlassian.bitbucket.server.bitbucket-ref-metadata:outgoing-pull-request-metadata" 39 | branch_related_issues = "com.atlassian.bitbucket.server.bitbucket-jira:branch-list-jira-issues" 40 | 41 | 42 | def is_can_removed_branch(branch_candidate): 43 | branch_id_name = branch_candidate.get('id') 44 | # Just exclude exist mainstream branches 45 | if any(x in branch_id_name for x in EXCLUDE_REPO_RULES): 46 | print(branch.get('displayId') + " in exclusion list") 47 | return False 48 | # skip default branch maybe DevOps made configs in ui 49 | if branch_candidate.get('isDefault'): 50 | print(branch.get('displayId') + " is default") 51 | return False 52 | pull_request_info = ((branch_candidate.get('metadata') or {}).get(out_going_pull_request) or {}) 53 | if pull_request_info.get('pullRequest') is not None or (pull_request_info.get('open') or 0) > 0: 54 | print(branch.get('displayId') + " has open PR") 55 | return False 56 | # skip branches without pull request info 57 | if pull_request_info is None or len(pull_request_info) == 0: 58 | print(branch.get('displayId') + " without pull request info") 59 | # return False 60 | 61 | author_time_stamp = branch_candidate.get('metadata').get(commit_info_key).get('authorTimestamp') 62 | # check latest commit info 63 | if time_now - author_time_stamp < delta_for_time_ms: 64 | print(branch.get('displayId') + " is too early to remove") 65 | return False 66 | 67 | # check issues statuses 68 | issues_in_metadata = branch_candidate.get('metadata').get(branch_related_issues) 69 | for issue in issues_in_metadata: 70 | if jira.get_issue_status(issue.get('key')) not in ACCEPTED_ISSUE_STATUSES: 71 | print(branch.get('displayId') + " related issue has not Resolution ") 72 | return False 73 | # so branch can be removed 74 | return True 75 | 76 | 77 | if __name__ == '__main__': 78 | DRY_RUN = False 79 | log = open("candidate_to_remove.csv", "w") 80 | log.write("'Branch name', 'Latest commit', 'Related issues has Resolution'\n") 81 | for repository in REPOS: 82 | step = 0 83 | limit = 10 84 | while flag: 85 | branches = stash.get_branches(PROJECT_KEY, repository, start=step * limit, limit=limit, 86 | order_by='ALPHABETICAL') 87 | if len(branches) == 0: 88 | flag = False 89 | break 90 | for branch in branches: 91 | display_id = branch['displayId'] 92 | committer_time_stamp = branch.get('metadata').get(commit_info_key).get('committerTimestamp') / 1000 93 | last_date_commit = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(committer_time_stamp)) 94 | if is_can_removed_branch(branch): 95 | if not DRY_RUN: 96 | stash.delete_branch(project=PROJECT_KEY, repository=repository, name=display_id, 97 | end_point=branch['latestCommit']) 98 | log.write("{},{},{}\n".format(display_id, last_date_commit, True)) 99 | step += 1 100 | log.close() 101 | print("Done") 102 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Atlassian Python API wrapper 3 | ============================ 4 | |Build Status| |PyPI version| |PyPI - Downloads| |License| |Codacy Badge| |Docs| 5 | 6 | Documentation 7 | ------------- 8 | 9 | `Documentation`_ 10 | 11 | .. _Documentation: https://atlassian-python-api.readthedocs.io 12 | 13 | Install 14 | ------- 15 | .. code-block:: console 16 | 17 | $ pip install atlassian-python-api 18 | 19 | Examples 20 | -------- 21 | **More examples in ``examples/`` directory.** 22 | 23 | Here's a short example of how to create a Confluence page: 24 | 25 | .. code-block:: python 26 | 27 | from atlassian import Confluence 28 | 29 | confluence = Confluence( 30 | url='http://localhost:8090', 31 | username='admin', 32 | password='admin') 33 | 34 | status = confluence.create_page( 35 | space='DEMO', 36 | title='This is the title', 37 | body='This is the body. You can use HTML tags!') 38 | 39 | print(status) 40 | 41 | And here's another example of how to get issues from Jira using JQL Query: 42 | 43 | .. code-block:: python 44 | 45 | from atlassian import Jira 46 | 47 | jira = Jira( 48 | url='http://localhost:8080', 49 | username='admin', 50 | password='admin') 51 | JQL = 'project = DEMO AND status IN ("To Do", "In Progress") ORDER BY issuekey' 52 | data = jira.jql(JQL) 53 | print(data) 54 | 55 | Also, you can use the Bitbucket module e.g. for getting project list 56 | 57 | .. code-block:: python 58 | 59 | from atlassian import Bitbucket 60 | 61 | bitbucket = Bitbucket( 62 | url='http://localhost:7990', 63 | username='admin', 64 | password='admin') 65 | 66 | data = bitbucket.project_list() 67 | print(data) 68 | 69 | Now you can use the Jira Service Desk module. See docs. 70 | Example to get your requests: 71 | 72 | .. code-block:: python 73 | 74 | from atlassian import ServiceDesk 75 | 76 | sd = ServiceDesk( 77 | url='http://localhost:7990', 78 | username='admin', 79 | password='admin') 80 | 81 | data = sd.get_my_customer_requests() 82 | print(data) 83 | 84 | Using Xray (Test Management tool for Jira): 85 | 86 | .. code-block:: python 87 | 88 | from atlassian import Xray 89 | 90 | xr = Xray( 91 | url='http://localhost:7990', 92 | username='admin', 93 | password='admin') 94 | 95 | data = xr.get_tests('TEST-001') 96 | print(data) 97 | 98 | If you want to see the response in pretty print format JSON. Feel free for use construction like: 99 | 100 | .. code-block:: python 101 | 102 | from pprint import pprint 103 | # you code here 104 | # and then print using pprint(result) instead of print(result) 105 | pprint(response) 106 | 107 | Development and Deployment (For contributors) 108 | --------------------------------------------- 109 | First of all, I am happy for any PR requests. 110 | Let's fork and provide your changes :) 111 | See the `Contribution Guidelines for this project`_ for details on how to make changes to this library. 112 | 113 | .. _Contribution Guidelines for this project: CONTRIBUTING.rst 114 | .. |Build Status| image:: https://travis-ci.org/atlassian-api/atlassian-python-api.svg?branch=master 115 | :target: https://pypi.python.org/pypi/atlassian-python-api 116 | :alt: Build status 117 | .. |PyPI version| image:: https://badge.fury.io/py/atlassian-python-api.svg 118 | :target: https://badge.fury.io/py/atlassian-python-api 119 | :alt: PyPI version 120 | .. |License| image:: https://img.shields.io/pypi/l/atlassian-python-api.svg 121 | :target: https://pypi.python.org/pypi/atlassian-python-api 122 | :alt: License 123 | .. |Codacy Badge| image:: https://api.codacy.com/project/badge/Grade/c822908f507544fe98ae37b25518ae3d 124 | :target: https://www.codacy.com/project/gonchik/atlassian-python-api/dashboard 125 | :alt: Codacy Badge 126 | .. |PyPI - Downloads| image:: https://pepy.tech/badge/atlassian-python-api/month 127 | :alt: PyPI - Downloads 128 | .. |Docs| image:: https://readthedocs.org/projects/atlassian-python-api/badge/?version=latest 129 | :target: https://atlassian-python-api.readthedocs.io/en/latest/?badge=latest 130 | :alt: Documentation Status 131 | 132 | 133 | Credits 134 | ------- 135 | In addition to all the contributors we would like to thank these vendors: 136 | 137 | * Atlassian_ for developing such a powerful ecosystem. 138 | * JetBrains_ for providing us with free licenses of PyCharm_ 139 | * Travis_ for hosting our continuous integration 140 | 141 | .. _Atlassian: https://www.atlassian.com/ 142 | .. _JetBrains: http://www.jetbrains.com 143 | .. _PyCharm: http://www.jetbrains.com/pycharm/ 144 | .. _Travis: https://travis-ci.org/ 145 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | import os 9 | import sys 10 | 11 | sys.path.insert(0, os.path.abspath('../..')) 12 | sys.path.insert(0, os.path.abspath('../atlassian/')) 13 | # -- Project information ----------------------------------------------------- 14 | 15 | project = 'Atlassian Python API' 16 | copyright = 'APACHE LICENSE, VERSION 2.0' 17 | author = 'SLRover' 18 | 19 | # The short X.Y version 20 | version = '' 21 | # The full version, including alpha/beta/rc tags 22 | release = '1.15.3' 23 | 24 | # -- General configuration --------------------------------------------------- 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # 28 | # needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | 'sphinx.ext.githubpages', 36 | ] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix(es) of source filenames. 42 | # You can specify multiple suffix as a list of string: 43 | # 44 | # source_suffix = ['.rst', '.md'] 45 | source_suffix = '.rst' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # The language for content autogenerated by Sphinx. Refer to documentation 51 | # for a list of supported languages. 52 | # 53 | # This is also used if you do content translation via gettext catalogs. 54 | # Usually you set "language" from the command line for these cases. 55 | language = "en" 56 | 57 | # List of patterns, relative to source directory, that match files and 58 | # directories to ignore when looking for source files. 59 | # This pattern also affects html_static_path and html_extra_path. 60 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 61 | 62 | # The name of the Pygments (syntax highlighting) style to use. 63 | pygments_style = None 64 | 65 | # -- Options for HTML output ------------------------------------------------- 66 | 67 | # The theme to use for HTML and HTML Help pages. See the documentation for 68 | # a list of builtin themes. 69 | # 70 | html_theme = 'alibaster' 71 | html_theme_options = { 72 | "rightsidebar": "false", 73 | } 74 | 75 | html_static_path = ['_static'] 76 | 77 | # Custom sidebar templates, must be a dictionary that maps document names 78 | # to template names. 79 | # 80 | # The default sidebars (for documents that don't match any pattern) are 81 | # defined by theme itself. Builtin themes are using these templates by 82 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 83 | # 'searchbox.html']``. 84 | # 85 | # html_sidebars = {} 86 | 87 | 88 | # -- Options for HTMLHelp output --------------------------------------------- 89 | 90 | # Output file base name for HTML help builder. 91 | htmlhelp_basename = 'AtlassianPythonAPIdoc' 92 | 93 | # -- Options for LaTeX output ------------------------------------------------ 94 | 95 | latex_elements = {} 96 | 97 | # Grouping the document tree into LaTeX files. List of tuples 98 | # (source start file, target name, title, 99 | # author, documentclass [howto, manual, or own class]). 100 | latex_documents = [ 101 | (master_doc, 'AtlassianPythonAPI.tex', 'Atlassian Python API Documentation', 102 | author, 'manual'), 103 | ] 104 | 105 | # -- Options for manual page output ------------------------------------------ 106 | 107 | # One entry per manual page. List of tuples 108 | # (source start file, name, description, authors, manual section). 109 | man_pages = [ 110 | (master_doc, 'atlassianpythonapi', 'Atlassian Python API Documentation', 111 | [author], 1) 112 | ] 113 | 114 | # -- Options for Texinfo output ---------------------------------------------- 115 | 116 | # Grouping the document tree into Texinfo files. List of tuples 117 | # (source start file, target name, title, author, 118 | # dir menu entry, description, category) 119 | texinfo_documents = [ 120 | (master_doc, 'AtlassianPythonAPI', 'Atlassian Python API Documentation', 121 | author, 'AtlassianPythonAPI', 'One line description of project.', 122 | 'Miscellaneous'), 123 | ] 124 | 125 | # -- Options for Epub output ------------------------------------------------- 126 | 127 | # Bibliographic Dublin Core info. 128 | epub_title = project 129 | 130 | # The unique identifier of the text. This can be a ISBN number 131 | # or the project homepage. 132 | # 133 | # epub_identifier = '' 134 | 135 | # A unique identification for the text. 136 | # 137 | # epub_uid = '' 138 | 139 | # A list of files that should not be packed into the epub file. 140 | epub_exclude_files = ['search.html'] 141 | 142 | # -- Extension configuration ------------------------------------------------- 143 | -------------------------------------------------------------------------------- /docs/xray.rst: -------------------------------------------------------------------------------- 1 | Xray module 2 | =========== 3 | 4 | .. NOTE: The Xray module only support the Server + Data Center edition of the Xray Jira plugin! 5 | 6 | Manage Test 7 | ----------- 8 | 9 | .. code-block:: python 10 | 11 | # Retrieve information about the provided tests 12 | xray.get_tests(['TEST-001', 'TEST-002']) 13 | 14 | # Retrieve a list of all Test Statuses available in Xray sorted by rank 15 | xray.get_test_statuses() 16 | 17 | # Retrieve test runs of a test 18 | xray.get_test_runs('TEST-001') 19 | 20 | # Retrieve test runs of a test filtered by tests environments 21 | xray.get_test_runs_with_environment('TEST-001', 'Android,iOS') 22 | 23 | # Retrieve pre-conditions of a test 24 | xray.get_test_preconditions('TEST-001') 25 | 26 | # Retrieve test sets associated with a test 27 | xray.get_test_sets('TEST-001') 28 | 29 | # Retrieve test executions of a test 30 | xray.get_test_executions('TEST-001') 31 | 32 | # Retrieve test plans associated with a test 33 | xray.get_test_plans('TEST-001') 34 | 35 | Manage Test Steps 36 | ----------------- 37 | 38 | .. code-block:: python 39 | 40 | # Retrieve the test step statuses available in Xray sorted by rank 41 | xray.get_test_step_statuses() 42 | 43 | # Retrieve the specified test step of a given test 44 | xray.get_test_step('TEST-001', 'STEP-001') 45 | 46 | # Retrieve the test steps of a given test 47 | xray.get_test_steps('TEST-001') 48 | 49 | # Create a new test steps for a given test 50 | xray.create_test_step('TEST-001', 'Example Test Step', 'Example Test Data', 'Example Test Result') 51 | 52 | # Update the specified test steps for a given test 53 | xray.update_test_step('TEST-001', 100, 'Updated Test Step', 'Updated Test Data', 'Updated Test Result') 54 | 55 | # Remove the specified test steps from a given test 56 | xray.delete_test_step('TEST-001', 100) 57 | 58 | Manage Pre-conditions 59 | --------------------- 60 | 61 | .. code-block:: python 62 | 63 | # Retrieve the tests associated with the given pre-condition 64 | xray.get_tests_with_precondition('PREC-001') 65 | 66 | # Associate tests with the given pre-condition 67 | xray.update_precondition('PREC-001', add=['TEST-001','TEST-002'], remove=['TEST-003']) 68 | 69 | # Remove association of the specified tests from the given pre-condition 70 | xray.delete_test_from_precondition('PREC-001', 'TEST-003') 71 | 72 | Manage Test sets 73 | ---------------- 74 | 75 | .. code-block:: python 76 | 77 | # Retrieve the tests associated with the given test set 78 | xray.get_tests_with_test_set('SET-001') 79 | 80 | # Associate tests with the given test set 81 | xray.update_test_set('SET-001',add=['TEST-001','TEST-002'], remove=['TEST-003']) 82 | 83 | # Remove association of the specified tests from the given test set 84 | xray.delete_test_from_test_set('SET-001', 'TEST-003') 85 | 86 | Manage Test plans 87 | ----------------- 88 | 89 | .. code-block:: python 90 | 91 | # Retrieve the tests associated with the given test plan 92 | xray.get_tests_with_test_plan('PLAN-001') 93 | 94 | # Associate tests with the given test plan 95 | xray.update_test_plan('PLAN-001', add=['TEST-001', 'TEST-002'], remove=['TEST-003']) 96 | 97 | # Remove association of the specified tests from the given test plan 98 | xray.delete_test_from_test_plan('PLAN-001', 'TEST-001'): 99 | 100 | Manage Test Executions 101 | ---------------------- 102 | 103 | .. code-block:: python 104 | 105 | # Retrieve the tests associated with the given test execution 106 | xray.get_tests_with_test_execution('EXEC-001') 107 | 108 | # Associate tests with the given test execution 109 | xray.update_test_execution('EXEC-001', add=['TEST-001', 'TEST-002'], remove=['TEST-003']) 110 | 111 | # Remove association of the specified tests from the given test execution 112 | xray.delete_test_from_test_execution('EXEC-001', 'TEST-001') 113 | 114 | Manage Test Runs 115 | ---------------- 116 | 117 | .. code-block:: python 118 | 119 | # Retrieve detailed information about the given test run 120 | xray.get_test_run(100) 121 | 122 | # Retrieve the assignee for the given test run. 123 | xray.get_test_run_assignee(100) 124 | 125 | # Update the assignee for the given test run 126 | xray.update_test_run_assignee(100, 'bob') 127 | 128 | # Retrieve the status for the given test run 129 | xray.get_test_run_status(100) 130 | 131 | # Update the status for the given test run 132 | xray.update_test_run_status(100, 'PASS') 133 | 134 | # Retrieve the defects for the given test run 135 | xray.get_test_run_defects(100) 136 | 137 | # Update the defects associated with the given test run 138 | xray.update_test_run_defects(100, add=['BUG-001', 'BUG-002'], remove=['BUG-003']) 139 | 140 | # Retrieve the comment for the given test run 141 | xray.get_test_run_comment(100) 142 | 143 | # Update the comment for the given test run 144 | xray.update_test_run_comment(100, 'Test needs to be reworked') 145 | 146 | # Retrieve the steps for the given test run 147 | xray.get_test_run_steps(100) 148 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | How to contribute 2 | ================= 3 | 4 | You’re very welcome to make bug fixes or enhancements to this library. 5 | This document lays out the guidelines for how to get those changes into 6 | the main package repository. 7 | 8 | Getting Started 9 | --------------- 10 | 11 | * Fork_ repository 12 | * Keep it sync_'ed while you are developing 13 | * Install pyenv_ 14 | * Install related atlassian product for testing through SDK_ or use the cloud instance 15 | * ``pip install -r requirements-dev.txt`` 16 | * Start up related product: 17 | - Standalone product atlas-run-standalone_ 18 | - For cloud product, just do registration 19 | * Send pull request 20 | 21 | .. _Fork: https://help.github.com/articles/fork-a-repo/ 22 | .. _sync: https://help.github.com/articles/syncing-a-fork/ 23 | .. _pyenv: https://amaral.northwestern.edu/resources/guides/pyenv-tutorial 24 | .. _SDK: https://developer.atlassian.com/server/framework/atlassian-sdk/downloads/ 25 | .. _atlas-run-standalone: https://developer.atlassian.com/server/framework/atlassian-sdk/atlas-run-standalone/ 26 | 27 | Mandatory conditions 28 | -------------------- 29 | 30 | 1. If you adding new method - add description to docs 31 | 2. If you make changes in current methods - add changes to docs 32 | 33 | Please follow the code style in the docs. 34 | 35 | 36 | Before you raise a PR 37 | --------------------- 38 | 39 | Create the **Commit Header** with the relevant Service Name pre-fixed, examples below, 40 | 41 | * Jira: review user module :heavy_check_mark: 42 | * [JIRA] Issues Move to Sprint :heavy_check_mark: 43 | * Confluence: update_page_property method :heavy_check_mark: 44 | 45 | An example of a commit message header, 46 | 47 | * Addition of parameters for start & limit in the function of `get_all_project_issues` :x: 48 | 49 | could be better written as, 50 | 51 | * [JIRA] Project Issues parameter addition for start and limit :heavy_check_mark: 52 | 53 | with the commit body have a detail about where/what changes introduced. 54 | 55 | This will help the reviewer or log-viewers to better identify what a particular commit is for. 56 | 57 | 58 | Using your changes before they’re live 59 | -------------------------------------- 60 | 61 | You may want to use the changes you’ve made to this library before the 62 | merging/review process has been completed. To do this you can install it 63 | into the global python environment by running this command from the top 64 | level directory. 65 | 66 | :: 67 | 68 | pip install . --upgrade 69 | 70 | The following command builds a package and uploads it to PIP repository. 71 | 72 | :: 73 | 74 | python setup.py sdist upload 75 | 76 | 77 | References 78 | ---------- 79 | 80 | All methods based on docs from: https://developer.atlassian.com/docs/ 81 | 82 | * `Jira Server`_ 83 | * `Jira Cloud`_ 84 | * `Confluence Server`_ 85 | * `Confluence Cloud`_ 86 | * `Jira Service Desk Server`_ 87 | * `Jira Service Desk Cloud`_ 88 | * `Portfolio for Jira`_ 89 | * `Portfolio for Jira Teams`_ 90 | * Bitbucket: 91 | - https://developer.atlassian.com/server/bitbucket/reference/rest-api/ 92 | - https://developer.atlassian.com/server/bitbucket/how-tos/command-line-rest/ 93 | - https://developer.atlassian.com/bitbucket/api/2/reference/resource/ 94 | * Bamboo: 95 | - https://docs.atlassian.com/atlassian-bamboo/REST/latest/ 96 | * Tempo: 97 | - https://www.tempo.io/server-api-documentation 98 | - http://tempo.io/doc/core/api/rest/latest/ 99 | * Marketplace: 100 | - https://developer.atlassian.com/platform/marketplace/rest 101 | * Crowd: 102 | - https://developer.atlassian.com/server/crowd/crowd-rest-apis/ 103 | * Xray: 104 | - https://docs.getxray.app/display/XRAY/REST+API 105 | * Others: 106 | - https://developer.atlassian.com/server/jira/platform/oauth/ 107 | - https://confluence.atlassian.com/cloud/api-tokens-938839638.html 108 | 109 | .. _`Jira Server`: https://docs.atlassian.com/software/jira/docs/api/REST/latest 110 | .. _`Jira Cloud`: https://developer.atlassian.com/cloud/jira/platform/rest/v3/ 111 | .. _`Confluence Server`: https://developer.atlassian.com/server/confluence/confluence-server-rest-api/ 112 | .. _`Confluence Cloud`: https://developer.atlassian.com/cloud/confluence/rest/ 113 | .. _`Jira Service Desk Cloud`: https://developer.atlassian.com/cloud/jira/service-desk/rest/ 114 | .. _`Jira Service Desk Server`: https://docs.atlassian.com/jira-servicedesk/REST/server 115 | .. _`Portfolio for Jira Teams`: https://docs.atlassian.com/portfolio-for-jira-server/REST/2.13.0/teams/ 116 | .. _`Portfolio for Jira`: https://docs.atlassian.com/portfolio-for-jira-server/REST/2.13.0/jpo/ 117 | 118 | 119 | Credits 120 | ------- 121 | In addition to all the contributors we would like to thank to these companies: 122 | 123 | * Atlassian_ for developing such a powerful ecosystem. 124 | * JetBrains_ for providing us with free licenses of PyCharm_ 125 | * Travis_ for hosting our continuous integration 126 | * Insomnia_ for providing the human rest client easy to test the methods 127 | .. _Atlassian: https://www.atlassian.com/ 128 | .. _JetBrains: http://www.jetbrains.com 129 | .. _PyCharm: http://www.jetbrains.com/pycharm/ 130 | .. _Travis: https://travis-ci.org/ 131 | .. _Insomnia: https://insomnia.rest/ -------------------------------------------------------------------------------- /docs/bamboo.rst: -------------------------------------------------------------------------------- 1 | Bamboo module 2 | ============= 3 | 4 | Projects & Plans 5 | ---------------- 6 | 7 | .. code-block:: python 8 | 9 | # Get all Projects 10 | projects(expand=None, favourite=False, clover_enabled=False, max_results=25) 11 | 12 | # Get a single project by the key 13 | project(project_key, expand=None, favourite=False, clover_enabled=False) 14 | 15 | # Get all build plans in a project 16 | project_plans(project_key) 17 | 18 | # Get all build plans 19 | plans(expand=None, favourite=False, clover_enabled=False, start_index=0, max_results=25) 20 | 21 | # Get information about plan build directory 22 | plan_directory_info(plan_key) 23 | 24 | # Get plan information 25 | get_plan(plan_key) 26 | 27 | # Delete a plan (or a plan branch) 28 | delete_plan(plan_key) 29 | 30 | # Disable plan 31 | disable_plan(plan_key) 32 | 33 | # Enable plan 34 | enable_plan(plan_key) 35 | 36 | Branches 37 | ------------- 38 | 39 | .. code-block:: python 40 | 41 | # Search Branches 42 | search_branches(plan_key, include_default_branch=True, max_results=25) 43 | 44 | # Get all plan Branches 45 | plan_branches(plan_key, expand=None, favourite=False, clover_enabled=False, max_results=25) 46 | 47 | # Get branch information 48 | get_branch_info(plan_key, branch_name) 49 | 50 | # Create new branch (vcs or simple) 51 | create_branch(plan_key, branch_name, vcs_branch=None, enabled=False, cleanup_enabled=False) 52 | 53 | # Get VCS Branches 54 | get_vcs_branches(plan_key, max_results=25) 55 | 56 | Build results 57 | ------------- 58 | 59 | .. code-block:: python 60 | 61 | # Get build results (Scalable from a single result to all build results) 62 | results(project_key=None, plan_key=None, job_key=None, build_number=None, expand=None, favourite=False, 63 | clover_enabled=False, issue_key=None, label=None, start_index=0, max_results=25, include_all_states=False) 64 | 65 | # Get latest build results 66 | latest_results(expand=None, favourite=False, clover_enabled=False, label=None, issue_key=None, 67 | start_index=0, max_results=25, include_all_states=False) 68 | 69 | # Get latest build results for the project 70 | project_latest_results(project_key, expand=None, favourite=False, clover_enabled=False, label=None, 71 | issue_key=None, start_index=0, max_results=25, include_all_states=False) 72 | 73 | # Get build results for a single plan 74 | plan_results(project_key, plan_key, expand=None, favourite=False, clover_enabled=False, label=None, 75 | issue_key=None, start_index=0, max_results=25, include_all_states=False) 76 | 77 | # Get a single build result 78 | build_result(build_key, expand=None, include_all_states=False) 79 | 80 | # Get latest results for a plan 81 | build_latest_result(plan_key, expand=None, include_all_states=False) 82 | 83 | # Delete build result 84 | delete_build_result(build_key) 85 | 86 | # Execute build 87 | execute_build(plan_key, stage=None, execute_all_stages=True, custom_revision=None, **bamboo_variables) 88 | 89 | Comments & Labels 90 | ----------------- 91 | 92 | .. code-block:: python 93 | 94 | # Get comments for the build 95 | comments(project_key, plan_key, build_number, start_index=0, max_results=25) 96 | 97 | # Make a comment 98 | create_comment(project_key, plan_key, build_number, comment, author=None) 99 | 100 | # Get labels for a build 101 | labels(project_key, plan_key, build_number, start_index=0, max_results=25) 102 | 103 | # Create a label 104 | create_label(project_key, plan_key, build_number, label) 105 | 106 | # Delete a label 107 | delete_label(project_key, plan_key, build_number, label) 108 | 109 | Deployments 110 | ----------- 111 | 112 | .. code-block:: python 113 | 114 | # Get deployment projects 115 | deployment_projects() 116 | 117 | # Get deployments for a single project 118 | deployment_project(project_id) 119 | 120 | # Get deployment environment results 121 | deployment_environment_results(env_id, expand=None, max_results=25) 122 | 123 | # Get deployment dashboard 124 | deployment_dashboard(project_id=None) 125 | 126 | Users & Groups 127 | -------------- 128 | 129 | .. code-block:: python 130 | 131 | # Get users in global permissions 132 | get_users_in_global_permissions(start=0, limit=25) 133 | 134 | # Get Groups 135 | get_groups(start=0, limit=25) 136 | 137 | # Create Group 138 | create_group(group_name) 139 | 140 | # Delete Group 141 | delete_group(group_name) 142 | 143 | # Add users into Group 144 | add_users_into_group(group_name, users) 145 | 146 | # Remove users from Group 147 | remove_users_from_group(group_name, users) 148 | 149 | # Get users from Group 150 | get_users_from_group(group_name, filter_users=None, start=0, limit=25) 151 | 152 | # Get users without Group 153 | get_users_not_in_group(group_name, filter_users='', start=0, limit=25) 154 | 155 | Other actions 156 | ------------- 157 | 158 | .. code-block:: python 159 | 160 | # Get build queue 161 | get_build_queue(expand='queuedBuilds') 162 | 163 | # Get server information 164 | server_info() 165 | 166 | # Get agents statuses 167 | agent_status() 168 | 169 | # Get activity 170 | activity() 171 | 172 | # Get custom expiry 173 | get_custom_expiry(limit=25) 174 | 175 | # Get reports 176 | reports(max_results=25) 177 | 178 | # Get charts 179 | hart(report_key, build_keys, group_by_period, date_filter=None, date_from=None, date_to=None, 180 | width=None, height=None, start_index=9, max_results=25) 181 | 182 | # Health check 183 | health_check() 184 | 185 | # Upload plugin 186 | upload_plugin(plugin_path) 187 | 188 | -------------------------------------------------------------------------------- /tests/test-confluence-attach.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import tempfile 3 | import json 4 | import os 5 | import unittest 6 | from atlassian import Confluence 7 | 8 | 9 | class TestConfluenceAttach(unittest.TestCase): 10 | secret_file = '../credentials.secret' 11 | 12 | ''' 13 | Keep the credentials private, the file is excluded. There is an example for credentials.secret 14 | See also: http://www.blacktechdiva.com/hide-api-keys/ 15 | 16 | { 17 | "host" : "https://localhost:8080", 18 | "username" : "john_doe", 19 | "password" : "12345678" 20 | } 21 | ''' 22 | 23 | def test_confluence_attach_file_1(self): 24 | credentials = None 25 | try: 26 | with open(self.secret_file) as json_file: 27 | credentials = json.load(json_file) 28 | except Exception as err: 29 | self.fail('[{0}]: {1}'.format(self.secret_file, err)) 30 | 31 | confluence = Confluence( 32 | url=credentials['host'], 33 | username=credentials['username'], 34 | password=credentials['password']) 35 | 36 | # individual configuration 37 | space = 'SAN' 38 | title = 'atlassian-python-rest-api-wrapper' 39 | 40 | # TODO: check if page are exists 41 | 42 | fd, filename = tempfile.mkstemp('w') 43 | os.write(fd, b'Hello World - Version 1') 44 | 45 | # upload a new file 46 | result = confluence.attach_file(filename, "", title=title, space=space, comment='upload from unittest') 47 | 48 | # attach_file() returns: {'results': [{'id': 'att144005326', 'type': 'attachment', ... 49 | self.assertTrue('results' in result) 50 | self.assertFalse('statusCode' in result) 51 | 52 | # upload a new version of an existing file 53 | os.lseek(fd, 0, 0) 54 | os.write(fd, b'Hello Universe - Version 2') 55 | result = confluence.attach_file(filename, "", title=title, space=space, comment='upload from unittest') 56 | 57 | # attach_file() returns: {'id': 'att144005326', 'type': 'attachment', ... 58 | self.assertTrue('id' in result) 59 | self.assertFalse('statusCode' in result) 60 | 61 | os.close(fd) 62 | os.remove(filename) 63 | 64 | def test_confluence_attach_file_2(self): 65 | credentials = None 66 | 67 | try: 68 | with open(self.secret_file) as json_file: 69 | credentials = json.load(json_file) 70 | except Exception as err: 71 | self.fail('[{0}]: {1}'.format(self.secret_file, err)) 72 | 73 | confluence = Confluence( 74 | url=credentials['host'], 75 | username=credentials['username'], 76 | password=credentials['password']) 77 | 78 | # individual configuration 79 | space = 'SAN' 80 | title = 'atlassian-python-rest-api-wrapper' 81 | 82 | # TODO: check if page are exists 83 | 84 | fd, filename = tempfile.mkstemp('w') 85 | os.write(fd, b'Hello World - Version 1') 86 | 87 | name = os.path.basename(tempfile.mktemp()) + ".txt" 88 | 89 | # upload a new file 90 | result = confluence.attach_file(filename, name, content_type='text/plain', title=title, space=space, 91 | comment='upload from unittest') 92 | 93 | # attach_file() returns: {'results': [{'id': 'att144005326', 'type': 'attachment', ... 94 | self.assertTrue('results' in result) 95 | self.assertFalse('statusCode' in result) 96 | 97 | # upload a new version of an existing file 98 | os.lseek(fd, 0, 0) 99 | os.write(fd, b'Hello Universe - Version 2') 100 | result = confluence.attach_file(filename, name, content_type='text/plain', title=title, space=space, 101 | comment='upload from unittest') 102 | 103 | # attach_file() returns: {'id': 'att144005326', 'type': 'attachment', ... 104 | self.assertTrue('id' in result) 105 | self.assertFalse('statusCode' in result) 106 | 107 | os.close(fd) 108 | os.remove(filename) 109 | 110 | def test_confluence_attach_content(self): 111 | credentials = None 112 | 113 | try: 114 | with open(self.secret_file) as json_file: 115 | credentials = json.load(json_file) 116 | except Exception as err: 117 | self.fail('[{0}]: {1}'.format(self.secret_file, err)) 118 | 119 | confluence = Confluence( 120 | url=credentials['host'], 121 | username=credentials['username'], 122 | password=credentials['password']) 123 | 124 | # individual configuration 125 | space = 'SAN' 126 | title = 'atlassian-python-rest-api-wrapper' 127 | 128 | attachment_name = os.path.basename(tempfile.mktemp()) 129 | 130 | # upload a new file 131 | content = b'Hello World - Version 1' 132 | result = confluence.attach_content(content, attachment_name, 'text/plain', title=title, space=space, 133 | comment='upload from unittest') 134 | 135 | # attach_file() returns: {'results': [{'id': 'att144005326', 'type': 'attachment', ... 136 | self.assertTrue('results' in result) 137 | self.assertFalse('statusCode' in result) 138 | 139 | # upload a new version of an existing file 140 | content = b'Hello Universe - Version 2' 141 | result = confluence.attach_content(content, attachment_name, 'text/plain', title=title, space=space, 142 | comment='upload from unittest') 143 | 144 | # attach_file() returns: {'id': 'att144005326', 'type': 'attachment', ... 145 | self.assertTrue('id' in result) 146 | self.assertFalse('statusCode' in result) 147 | 148 | 149 | if __name__ == '__main__': 150 | unittest.main() 151 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Atlassian Python API documentation master file, created by 2 | sphinx-quickstart on Thu Sep 13 13:43:20 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Atlassian Python API's documentation! 7 | ================================================ 8 | |Build Status| |PyPI version| |PyPI - Downloads| |License| |Codacy Badge| |Docs| 9 | 10 | Getting started 11 | --------------- 12 | 13 | Install package using pip: 14 | 15 | ``pip install atlassian-python-api`` 16 | 17 | Add a connection: 18 | 19 | .. code-block:: python 20 | 21 | from atlassian import Jira 22 | from atlassian import Confluence 23 | from atlassian import Bitbucket 24 | from atlassian import ServiceDesk 25 | from atlassian import Xray 26 | 27 | jira = Jira( 28 | url='http://localhost:8080', 29 | username='admin', 30 | password='admin') 31 | 32 | confluence = Confluence( 33 | url='http://localhost:8090', 34 | username='admin', 35 | password='admin') 36 | 37 | bitbucket = Bitbucket( 38 | url='http://localhost:7990', 39 | username='admin', 40 | password='admin') 41 | 42 | service_desk = ServiceDesk( 43 | url='http://localhost:8080', 44 | username='admin', 45 | password='admin') 46 | 47 | xray = Xray( 48 | url='http://localhost:8080', 49 | username='admin', 50 | password='admin') 51 | 52 | 53 | Key/Cert Based authentication 54 | ----------------------------- 55 | 56 | Add a connection using key/cert based authentication: 57 | 58 | .. code-block:: python 59 | 60 | from atlassian import Jira 61 | from atlassian import Confluence 62 | from atlassian import Bitbucket 63 | from atlassian import ServiceDesk 64 | from atlassian import Xray 65 | 66 | jira = Jira( 67 | url='http://localhost:8080', 68 | key='/path/to/key', 69 | cert='/path/to/cert') 70 | 71 | confluence = Confluence( 72 | url='http://localhost:8090', 73 | key='/path/to/key', 74 | cert='/path/to/cert') 75 | 76 | bitbucket = Bitbucket( 77 | url='http://localhost:7990', 78 | key='/path/to/key', 79 | cert='/path/to/cert') 80 | 81 | service_desk = ServiceDesk( 82 | url='http://localhost:8080', 83 | key='/path/to/key', 84 | cert='/path/to/cert') 85 | 86 | xray = Xray( 87 | url='http://localhost:8080', 88 | key='/path/to/key', 89 | cert='/path/to/cert') 90 | 91 | Alternatively OAuth can be used: 92 | 93 | .. code-block:: python 94 | 95 | oauth_dict = { 96 | 'access_token': 'access_token', 97 | 'access_token_secret': 'access_token_secret', 98 | 'consumer_key': 'consumer_key', 99 | 'key_cert': 'key_cert'} 100 | 101 | jira = Jira( 102 | url='http://localhost:8080', 103 | oauth=oauth_dict) 104 | 105 | confluence = Confluence( 106 | url='http://localhost:8090', 107 | oauth=oauth_dict) 108 | 109 | bitbucket = Bitbucket( 110 | url='http://localhost:7990', 111 | oauth=oauth_dict) 112 | 113 | service_desk = ServiceDesk( 114 | url='http://localhost:8080', 115 | oauth=oauth_dict) 116 | 117 | xray = Xray( 118 | url='http://localhost:8080', 119 | oauth=oauth_dict) 120 | 121 | Or Kerberos *(installation with kerberos extra necessary)*: 122 | 123 | .. code-block:: python 124 | 125 | kerberos_service = 'HTTP/jira.localhost@YOUR.DOMAIN.COM' 126 | 127 | jira = Jira( 128 | url='http://localhost:8080', 129 | kerberos=kerberos_service) 130 | 131 | confluence = Confluence( 132 | url='http://localhost:8090', 133 | kerberos=kerberos_service) 134 | 135 | bitbucket = Bitbucket( 136 | url='http://localhost:7990', 137 | kerberos=kerberos_service) 138 | 139 | service_desk = ServiceDesk( 140 | url='http://localhost:8080', 141 | kerberos=kerberos_service) 142 | 143 | xray = Xray( 144 | url='http://localhost:8080', 145 | kerberos=kerberos_service) 146 | 147 | Or reuse cookie file: 148 | 149 | .. code-block:: python 150 | 151 | from atlassian import utils 152 | cookie_dict = utils.parse_cookie_file("cookie.txt") 153 | 154 | jira = Jira( 155 | url='http://localhost:8080', 156 | cookies=cookie_dict) 157 | 158 | confluence = Confluence( 159 | url='http://localhost:8090', 160 | cookies=cookie_dict) 161 | 162 | bitbucket = Bitbucket( 163 | url='http://localhost:7990', 164 | cookies=cookie_dict) 165 | 166 | service_desk = ServiceDesk( 167 | url='http://localhost:8080', 168 | cookies=cookie_dict) 169 | 170 | xray = Xray( 171 | url='http://localhost:8080', 172 | cookies=cookie_dict) 173 | 174 | To authenticate to the Atlassian Cloud APIs: 175 | 176 | .. code-block:: python 177 | 178 | # Obtain an API token from: https://id.atlassian.com/manage-profile/security/api-tokens 179 | 180 | jira = Jira( 181 | url='https://your-domain.atlassian.net', 182 | username=jira_username, 183 | password=jira_api_token, 184 | cloud=True) 185 | 186 | confluence = Confluence( 187 | url='https://your-domain.atlassian.net', 188 | username=jira_username, 189 | password=jira_api_token, 190 | cloud=True) 191 | 192 | bitbucket = Bitbucket( 193 | url='https://your-domain.atlassian.net', 194 | username=jira_username, 195 | password=jira_api_token, 196 | cloud=True) 197 | 198 | service_desk = ServiceDesk( 199 | url='https://your-domain.atlassian.net', 200 | username=jira_username, 201 | password=jira_api_token, 202 | cloud=True) 203 | 204 | .. toctree:: 205 | :maxdepth: 2 206 | 207 | jira 208 | confluence 209 | bitbucket 210 | bamboo 211 | service_desk 212 | xray 213 | 214 | .. |Build Status| image:: https://travis-ci.org/atlassian-api/atlassian-python-api.svg?branch=master 215 | :target: https://pypi.python.org/pypi/atlassian-python-api 216 | :alt: Build status 217 | .. |PyPI version| image:: https://badge.fury.io/py/atlassian-python-api.svg 218 | :target: https://badge.fury.io/py/atlassian-python-api 219 | :alt: PyPI version 220 | .. |License| image:: https://img.shields.io/pypi/l/atlassian-python-api.svg 221 | :target: https://pypi.python.org/pypi/atlassian-python-api 222 | :alt: License 223 | .. |Codacy Badge| image:: https://api.codacy.com/project/badge/Grade/c822908f507544fe98ae37b25518ae3d 224 | :target: https://www.codacy.com/project/gonchik/atlassian-python-api/dashboard?utm_source=github.com&utm_medium=referral&utm_content=AstroMatt/atlassian-python-api&utm_campaign=Badge_Grade_Dashboard 225 | :alt: Codacy Badge 226 | .. |PyPI - Downloads| image:: https://pepy.tech/badge/atlassian-python-api/month 227 | :alt: PyPI - Downloads 228 | .. |Docs| image:: https://readthedocs.org/projects/atlassian-python-api/badge/?version=latest 229 | :target: https://atlassian-python-api.readthedocs.io/en/latest/?badge=latest 230 | :alt: Documentation Status 231 | -------------------------------------------------------------------------------- /docs/service_desk.rst: -------------------------------------------------------------------------------- 1 | Jira Service Desk module 2 | ======================== 3 | 4 | Get info about Service Desk 5 | --------------------------- 6 | 7 | .. code-block:: python 8 | 9 | # Get info about Service Desk app 10 | sd.get_info() 11 | 12 | # Get all service desks in the JIRA Service Desk application with the option to include archived service desks 13 | sd.get_service_desks() 14 | 15 | # Get the service desk for a given service desk ID 16 | sd.get_service_desk_by_id(service_desk_id) 17 | 18 | Create customer 19 | --------------- 20 | 21 | **EXPERIMENTAL** (may change without notice) 22 | 23 | .. code-block:: python 24 | 25 | sd.create_customer(full_name, email) 26 | 27 | The Request actions 28 | ------------------- 29 | 30 | .. code-block:: python 31 | 32 | # Create customer request 33 | sd.create_customer_request(service_desk_id, request_type_id, values_dict, raise_on_behalf_of=None, request_participants=None) 34 | 35 | # Get customer request by ID 36 | sd.get_customer_request(issue_id_or_key) 37 | 38 | # Get customer requests 39 | sd.get_my_customer_requests() 40 | 41 | # Get customer request status 42 | sd.get_customer_request_status(issue_id_or_key) 43 | 44 | # Create comment. Optional argument public (True or False), default is True 45 | sd.create_request_comment(issue_id_or_key, body, public=True) 46 | 47 | # Get request comments 48 | sd.get_request_comments(issue_id_or_key) 49 | 50 | # Get request comment 51 | sd.get_request_comment_by_id(issue_id_or_key, comment_id) 52 | 53 | Manage a Participants 54 | --------------------- 55 | 56 | .. code-block:: python 57 | 58 | # Get request participants 59 | sd.get_request_participants(issue_id_or_key, start=0, limit=50) 60 | 61 | # Add request participants 62 | # The calling user must have permission to manage participants for this customer request 63 | sd.add_request_participants(issue_id_or_key, users_list) 64 | 65 | # Remove request participants 66 | # The calling user must have permission to manage participants for this customer request 67 | sd.remove_request_participants(issue_id_or_key, users_list) 68 | 69 | Transitions 70 | ----------- 71 | 72 | **EXPERIMENTAL** (may change without notice) 73 | 74 | .. code-block:: python 75 | 76 | # Get customer transitions. A list of transitions that customers can perform on the request 77 | sd.get_customer_transitions(issue_id_or_key) 78 | 79 | # Perform transition. Optional argument comment (string), default is None 80 | sd.perform_transition(issue_id_or_key, transition_id, comment=None) 81 | 82 | Manage the Organizations 83 | ------------------------ 84 | 85 | **EXPERIMENTAL** (may change without notice) 86 | 87 | .. code-block:: python 88 | 89 | # Get a list of organizations in the JIRA instance 90 | # If the user is not an agent, the resource returns a list of organizations the user is a member of 91 | # If service_desk_id is None, request returns all organizations 92 | # In service_desk_id is ID, request returns organizations from given Service Desk ID 93 | sd.get_organisations(service_desk_id=None, start=0, limit=50) 94 | 95 | # Get an organization for a given organization ID 96 | sd.get_organization(organization_id) 97 | 98 | # Get all the users of a specified organization 99 | sd.get_users_in_organization(organization_id, start=0, limit=50) 100 | 101 | # Create organization 102 | sd.create_organization(name) 103 | 104 | # Add an organization to a servicedesk for a given servicedesk ID (str) and organization ID (int) 105 | sd.add_organization(service_desk_id, organization_id) 106 | 107 | # Remove an organization from a servicedesk for a given servicedesk ID (str) and organization ID (int) 108 | sd.remove_organization(service_desk_id, organization_id) 109 | 110 | # Delete organization 111 | sd.delete_organization(organization_id) 112 | 113 | # Add users to organization 114 | sd.add_users_to_organization(organization_id, users_list) 115 | 116 | # Remove users from organization 117 | sd.remove_users_from_organization(organization_id, users_list) 118 | 119 | Attachment actions 120 | ------------------ 121 | 122 | **EXPERIMENTAL** (may change without notice) 123 | 124 | .. code-block:: python 125 | 126 | # Create attachment (only single file) as a comment 127 | # You can choose type of attachment. public=True is Public attachment, public=False is Internal attachment 128 | # Customers can only create public attachments 129 | # An additional comment may be provided which will be prepended to the attachments 130 | sd.create_attachment(service_desk_id, issue_id_or_key, filename, public=True, comment=None) 131 | 132 | # Create temporary attachment, which can later be converted into permanent attachment 133 | sd.attach_temporary_file(service_desk_id, filename) 134 | 135 | # Add temporary attachment that were created using attach_temporary_file function to a customer request 136 | sd.add_attachment(issue_id_or_key, temp_attachment_id, public=True, comment=None) 137 | 138 | SLA actions 139 | ----------- 140 | 141 | .. code-block:: python 142 | 143 | # Get the SLA information for a customer request for a given request ID or key 144 | # IMPORTANT: The calling user must be an agent 145 | sd.get_sla(issue_id_or_key, start=0, limit=50) 146 | 147 | # Get the SLA information for a customer request for a given request ID or key and SLA metric ID 148 | # IMPORTANT: The calling user must be an agent 149 | sd.get_sla_by_id(issue_id_or_key, sla_id) 150 | 151 | Approvals 152 | --------- 153 | 154 | .. code-block:: python 155 | 156 | # Get all approvals on a request, for a given request ID/Key 157 | sd.get_approvals(issue_id_or_key, start=0, limit=50) 158 | 159 | # Get an approval for a given approval ID 160 | sd.get_approval_by_id(issue_id_or_key, approval_id) 161 | 162 | # Answer a pending approval 163 | sd.answer_approval(issue_id_or_key, approval_id, decision) 164 | 165 | Queues 166 | ------ 167 | 168 | .. code-block:: python 169 | 170 | # Get queue settings on project 171 | sd.get_queue_settings(project_key) 172 | 173 | **EXPERIMENTAL** (may change without notice) 174 | 175 | .. code-block:: python 176 | 177 | # Returns a page of queues defined inside a service desk, for a given service desk ID. 178 | # The returned queues will include an issue count for each queue (represented in issueCount field) 179 | # if the query param includeCount is set to true (defaults to false). 180 | # Permissions: The calling user must be an agent of the given service desk. 181 | sd.get_queues(service_desk_id, include_count=False, start=0, limit=50) 182 | 183 | # Returns a page of issues inside a queue for a given queue ID. 184 | # Only fields that the queue is configured to show are returned. 185 | # For example, if a queue is configured to show only Description and Due Date, 186 | # then only those two fields are returned for each issue in the queue. 187 | # Permissions: The calling user must have permission to view the requested queue, 188 | # i.e. they must be an agent of the service desk that the queue belongs to. 189 | sd.get_issues_in_queue(service_desk_id, queue_id, start=0, limit=50) 190 | 191 | Add customers to given Service Desk 192 | ----------------------------------- 193 | 194 | **EXPERIMENTAL** (may change without notice) 195 | 196 | .. code-block:: python 197 | 198 | # Adds one or more existing customers to the given service desk. 199 | # If you need to create a customer, see Create customer method. 200 | # Administer project permission is required, or agents if public signups 201 | # and invites are enabled for the Service Desk project. 202 | sd.add_customers(service_desk_id, list_of_usernames) 203 | -------------------------------------------------------------------------------- /docs/confluence.rst: -------------------------------------------------------------------------------- 1 | Confluence module 2 | ================= 3 | 4 | Get page info 5 | ------------- 6 | 7 | .. code-block:: python 8 | 9 | # Check page exists 10 | confluence.page_exists(space, title) 11 | 12 | # Provide content by type (page, blog, comment) 13 | confluence.get_page_child_by_type(page_id, type='page', start=None, limit=None) 14 | 15 | # Provide content id from search result by title and space 16 | confluence.get_page_id(space, title) 17 | 18 | # Provide space key from content id 19 | confluence.get_page_space(page_id) 20 | 21 | # Returns the list of labels on a piece of Content 22 | confluence.get_page_by_title(space, title, start=None, limit=None) 23 | 24 | # Get page by ID 25 | # Example request URI(s): 26 | # http://example.com/confluence/rest/api/content/1234?expand=space,body.view,version,container 27 | # http://example.com/confluence/rest/api/content/1234?status=any 28 | # page_id: Content ID 29 | # status: (str) list of Content statuses to filter results on. Default value: [current] 30 | # version: (int) 31 | # expand: OPTIONAL: A comma separated list of properties to expand on the content. 32 | # Default value: history,space,version 33 | # We can also specify some extensions such as extensions.inlineProperties 34 | # (for getting inline comment-specific properties) or extensions.resolution 35 | # for the resolution status of each comment in the results 36 | confluence.get_page_by_id(self, page_id, expand=None, status=None, version=None) 37 | 38 | # The list of labels on a piece of Content 39 | confluence.get_page_labels(page_id, prefix=None, start=None, limit=None) 40 | 41 | # Get draft page by ID 42 | confluence.get_draft_page_by_id(page_id, status='draft') 43 | 44 | # Get all page by label 45 | confluence.get_all_pages_by_label(label, start=0, limit=50) 46 | 47 | # Get all pages from Space 48 | # content_type can be 'page' or 'blogpost'. Defaults to 'page' 49 | # expand is a comma separated list of properties to expand on the content. 50 | # max limit is 100. For more you have to loop over start values. 51 | confluence.get_all_pages_from_space(space, start=0, limit=100, status=None, expand=None, content_type='page') 52 | 53 | # Get list of pages from trash 54 | confluence.get_all_pages_from_space_trash(space, start=0, limit=500, status='trashed', content_type='page') 55 | 56 | # Get list of draft pages from space 57 | # Use case is cleanup old drafts from Confluence 58 | confluence.get_all_draft_pages_from_space(space, start=0, limit=500, status='draft') 59 | 60 | # Search list of draft pages by space key 61 | # Use case is cleanup old drafts from Confluence 62 | confluence.get_all_draft_pages_from_space_through_cql(space, start=0, limit=500, status='draft') 63 | 64 | # Info about all restrictions by operation 65 | confluence.get_all_restrictions_for_content(content_id) 66 | 67 | Page actions 68 | ------------ 69 | 70 | .. code-block:: python 71 | 72 | # Create page from scratch 73 | confluence.create_page(space, title, body, parent_id=None, type='page', representation='storage', editor='v2') 74 | 75 | # This method removes a page, if it has recursive flag, method removes including child pages 76 | confluence.remove_page(page_id, status=None, recursive=False) 77 | 78 | # Remove any content 79 | confluence.remove_content(content_id): 80 | 81 | # Remove page from trash 82 | confluence.remove_page_from_trash(page_id) 83 | 84 | # Remove page as draft 85 | confluence.remove_page_as_draft(page_id) 86 | 87 | # Update page if already exist 88 | confluence.update_page(page_id, title, body, parent_id=None, type='page', representation='storage', minor_edit=False) 89 | 90 | # Update page or create page if it is not exists 91 | confluence.update_or_create(parent_id, title, body, representation='storage') 92 | 93 | # Append body to page if already exist 94 | confluence.append_page(self, page_id, title, append_body, parent_id=None, type='page', representation='storage', minor_edit=False) 95 | 96 | # Set the page (content) property e.g. add hash parameters 97 | confluence.set_page_property(page_id, data) 98 | 99 | # Delete the page (content) property e.g. delete key of hash 100 | confluence.delete_page_property(page_id, page_property) 101 | 102 | # Move page 103 | confluence.move_page(space_key, page_id, target_title, position="append") 104 | 105 | # Get the page (content) property e.g. get key of hash 106 | confluence.get_page_property(page_id, page_property_key) 107 | 108 | # Get the page (content) properties 109 | confluence.get_page_properties(page_id) 110 | 111 | # Get page ancestors 112 | confluence.get_page_ancestors(page_id) 113 | 114 | # Attach (upload) a file to a page, if it exists it will update the 115 | # automatically version the new file and keep the old one 116 | confluence.attach_file(filename, name=None, content_type=None, page_id=None, title=None, space=None, comment=None) 117 | 118 | # Attach (upload) a content to a page, if it exists it will update the 119 | # automatically version the new file and keep the old one 120 | confluence.attach_content(content, name=None, content_type=None, page_id=None, title=None, space=None, comment=None) 121 | 122 | # Remove completely a file if version is None or delete version 123 | confluence.delete_attachment(page_id, filename, version=None) 124 | 125 | # Remove completely a file if version is None or delete version 126 | confluence.delete_attachment_by_id(attachment_id, version) 127 | 128 | # Keep last versions 129 | confluence.remove_page_attachment_keep_version(page_id, filename, keep_last_versions) 130 | 131 | # Get attachment history 132 | confluence.get_attachment_history(attachment_id, limit=200, start=0) 133 | 134 | # Get attachment for content 135 | confluence.get_attachments_from_content(page_id, start=0, limit=50, expand=None, filename=None, media_type=None) 136 | 137 | # Check has unknown attachment error on page 138 | confluence.has_unknown_attachment_error(page_id) 139 | 140 | # Export page as PDF 141 | # api_version needs to be set to 'cloud' when exporting from Confluence Cloud. 142 | confluence.export_page(page_id) 143 | 144 | # Set a label on the page 145 | confluence.set_page_label(page_id, label) 146 | 147 | # Delete Confluence page label 148 | confluence.remove_page_label(page_id, label) 149 | 150 | # Add comment into page 151 | confluence.add_comment(page_id, text) 152 | 153 | Get spaces info 154 | --------------- 155 | 156 | .. code-block:: python 157 | 158 | # Get all spaces with provided limit 159 | # additional info, e.g. metadata, icon, description, homepage 160 | confluence.get_all_spaces(start=0, limit=500, expand=None) 161 | 162 | # Get information about a space through space key 163 | confluence.get_space(space_key, expand='description.plain,homepage') 164 | 165 | # Get space content (configuring by the expand property) 166 | confluence.get_space_content(space_key, depth="all", start=0, limit=500, content_type=None, expand="body.storage") 167 | 168 | # Get Space permissions set based on json-rpc call 169 | confluence.get_space_permissions(space_key) 170 | 171 | Users and Groups 172 | ---------------- 173 | 174 | .. code-block:: python 175 | 176 | # Get all groups from Confluence User management 177 | confluence.get_all_groups(start=0, limit=1000) 178 | 179 | # Get a paginated collection of users in the given group 180 | confluence.get_group_members(group_name='confluence-users', start=0, limit=1000) 181 | 182 | # Get information about a user through username 183 | confluence.get_user_details_by_username(username, expand=None) 184 | 185 | # Get information about a user through user key 186 | confluence.get_user_details_by_userkey(userkey, expand=None) 187 | 188 | CQL 189 | --- 190 | 191 | .. code-block:: python 192 | 193 | # Get results from cql search result with all related fields 194 | confluence.cql(cql, start=0, limit=None, expand=None, include_archived_spaces=None, excerpt=None) 195 | 196 | Other actions 197 | ------------- 198 | 199 | .. code-block:: python 200 | 201 | # Clean all caches from cache management 202 | confluence.clean_all_caches() 203 | 204 | # Clean caches from cache management 205 | # e.g. 206 | # com.gliffy.cache.gon 207 | # org.hibernate.cache.internal.StandardQueryCache_v5 208 | confluence.clean_package_cache(cache_name='com.gliffy.cache.gon') 209 | 210 | # Convert to Confluence XHTML format from wiki style 211 | confluence.convert_wiki_to_storage(wiki) 212 | 213 | # Get page history 214 | confluence.history(page_id) 215 | 216 | # Get content history by version number. It works as experimental method 217 | confluence.get_content_history_by_version_number(content_id, version_number) 218 | 219 | # Remove content history. It works as experimental method 220 | confluence.remove_content_history(page_id, version_number) 221 | 222 | # Compare content and check is already updated or not 223 | confluence.is_page_content_is_already_updated(page_id, body) 224 | 225 | # Add inline task setting checkbox method 226 | confluence.set_inline_tasks_checkbox(page_id, task_id, status) 227 | 228 | -------------------------------------------------------------------------------- /atlassian/rest_client.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import logging 3 | import requests 4 | from json import dumps 5 | from oauthlib.oauth1 import SIGNATURE_RSA 6 | from requests_oauthlib import OAuth1 7 | from six.moves.urllib.parse import urlencode 8 | 9 | from atlassian.request_utils import get_default_logger 10 | 11 | log = get_default_logger(__name__) 12 | 13 | 14 | class AtlassianRestAPI(object): 15 | default_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'} 16 | experimental_headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 17 | 'X-ExperimentalApi': 'opt-in'} 18 | form_token_headers = {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 19 | 'X-Atlassian-Token': 'no-check'} 20 | no_check_headers = {'X-Atlassian-Token': 'no-check'} 21 | safe_mode_headers = {'X-Atlassian-Token': 'nocheck', 22 | 'Content-Type': 'application/vnd.atl.plugins.safe.mode.flag+json'} 23 | experimental_headers_general = {'X-Atlassian-Token': 'no-check', 'X-ExperimentalApi': 'opt-in'} 24 | response = None 25 | 26 | def __init__(self, url, username=None, password=None, timeout=60, api_root='rest/api', api_version='latest', 27 | verify_ssl=True, session=None, oauth=None, cookies=None, advanced_mode=None, kerberos=None, 28 | cloud=False, proxies=None): 29 | if ('atlassian.net' in url or 'jira.com' in url) \ 30 | and '/wiki' not in url \ 31 | and self.__class__.__name__ in 'Confluence': 32 | url = self.url_joiner(url, '/wiki') 33 | self.url = url 34 | self.username = username 35 | self.password = password 36 | self.timeout = int(timeout) 37 | self.verify_ssl = verify_ssl 38 | self.api_root = api_root 39 | self.api_version = api_version 40 | self.cookies = cookies 41 | self.advanced_mode = advanced_mode 42 | self.cloud = cloud 43 | self.proxies = proxies 44 | if session is None: 45 | self._session = requests.Session() 46 | else: 47 | self._session = session 48 | if username and password: 49 | self._create_basic_session(username, password) 50 | elif oauth is not None: 51 | self._create_oauth_session(oauth) 52 | elif kerberos is not None: 53 | self._create_kerberos_session(kerberos) 54 | elif cookies is not None: 55 | self._session.cookies.update(cookies) 56 | 57 | def _create_basic_session(self, username, password): 58 | self._session.auth = (username, password) 59 | 60 | def _create_kerberos_session(self, kerberos_service): 61 | try: 62 | import kerberos as kerb 63 | except ImportError as e: 64 | log.debug(e) 65 | try: 66 | import kerberos_sspi as kerb 67 | except ImportError: 68 | raise ImportError("No kerberos implementation available") 69 | __, krb_context = kerb.authGSSClientInit(kerberos_service) 70 | kerb.authGSSClientStep(krb_context, "") 71 | auth_header = ("Negotiate " + kerb.authGSSClientResponse(krb_context)) 72 | self._update_header("Authorization", auth_header) 73 | response = self._session.get(self.url, verify=self.verify_ssl) 74 | response.raise_for_status() 75 | 76 | def _create_oauth_session(self, oauth_dict): 77 | oauth = OAuth1(oauth_dict['consumer_key'], 78 | rsa_key=oauth_dict['key_cert'], signature_method=SIGNATURE_RSA, 79 | resource_owner_key=oauth_dict['access_token'], 80 | resource_owner_secret=oauth_dict['access_token_secret']) 81 | self._session.auth = oauth 82 | 83 | def _update_header(self, key, value): 84 | """ 85 | Update header for exist session 86 | :param key: 87 | :param value: 88 | :return: 89 | """ 90 | self._session.headers.update({key: value}) 91 | 92 | def log_curl_debug(self, method, url, data=None, headers=None, level=logging.DEBUG): 93 | """ 94 | 95 | :param method: 96 | :param url: 97 | :param data: 98 | :param headers: 99 | :param level: 100 | :return: 101 | """ 102 | headers = headers or self.default_headers 103 | message = "curl --silent -X {method} -H {headers} {data} '{url}'".format( 104 | method=method, 105 | headers=' -H '.join(["'{0}: {1}'".format(key, value) for key, value in headers.items()]), 106 | data='' if not data else "--data '{0}'".format(dumps(data)), 107 | url=url) 108 | log.log(level=level, msg=message) 109 | 110 | def resource_url(self, resource): 111 | return '/'.join([self.api_root, self.api_version, resource]) 112 | 113 | @staticmethod 114 | def url_joiner(url, path, trailing=None): 115 | url_link = '/'.join(s.strip('/') for s in [url, path]) 116 | if trailing: 117 | url_link += '/' 118 | return url_link 119 | 120 | def request(self, method='GET', path='/', data=None, json=None, flags=None, params=None, headers=None, 121 | files=None, trailing=None): 122 | """ 123 | 124 | :param method: 125 | :param path: 126 | :param data: 127 | :param json: 128 | :param flags: 129 | :param params: 130 | :param headers: 131 | :param files: 132 | :param trailing: bool 133 | :return: 134 | """ 135 | url = self.url_joiner(self.url, path, trailing) 136 | if params or flags: 137 | url += '?' 138 | if params: 139 | url += urlencode(params or {}) 140 | if flags: 141 | url += ('&' if params else '') + '&'.join(flags or []) 142 | json_dump = None 143 | if files is None: 144 | data = None if not data else dumps(data) 145 | json_dump = None if not json else dumps(json) 146 | self.log_curl_debug(method=method, url=url, headers=headers, 147 | data=data if data else json_dump) 148 | 149 | headers = headers or self.default_headers 150 | response = self._session.request( 151 | method=method, 152 | url=url, 153 | headers=headers, 154 | data=data, 155 | json=json, 156 | timeout=self.timeout, 157 | verify=self.verify_ssl, 158 | files=files, 159 | proxies=self.proxies 160 | ) 161 | response.encoding = 'utf-8' 162 | 163 | if self.advanced_mode: 164 | return response 165 | 166 | log.debug("HTTP: {} {} -> {} {}".format(method, path, response.status_code, response.reason)) 167 | response.raise_for_status() 168 | return response 169 | 170 | def get(self, path, data=None, flags=None, params=None, headers=None, not_json_response=None, trailing=None): 171 | """ 172 | Get request based on the python-requests module. You can override headers, and also, get not json response 173 | :param path: 174 | :param data: 175 | :param flags: 176 | :param params: 177 | :param headers: 178 | :param not_json_response: OPTIONAL: For get content from raw requests packet 179 | :param trailing: OPTIONAL: for wrap slash symbol in the end of string 180 | :return: 181 | """ 182 | response = self.request('GET', path=path, flags=flags, params=params, data=data, headers=headers, 183 | trailing=trailing) 184 | if self.advanced_mode: 185 | return response 186 | if not_json_response: 187 | return response.content 188 | else: 189 | if not response.text: 190 | return None 191 | try: 192 | return response.json() 193 | except Exception as e: 194 | log.error(e) 195 | return response.text 196 | 197 | def post(self, path, data=None, json=None, headers=None, files=None, params=None, trailing=None): 198 | response = self.request('POST', path=path, data=data, json=json, headers=headers, files=files, params=params, 199 | trailing=trailing) 200 | if self.advanced_mode: 201 | return response 202 | try: 203 | return response.json() 204 | except ValueError: 205 | log.debug('Received response with no content') 206 | return None 207 | except Exception as e: 208 | log.error(e) 209 | return None 210 | 211 | def put(self, path, data=None, headers=None, files=None, trailing=None, params=None): 212 | response = self.request('PUT', path=path, data=data, headers=headers, files=files, params=params, 213 | trailing=trailing) 214 | if self.advanced_mode: 215 | return response 216 | try: 217 | return response.json() 218 | except ValueError: 219 | log.debug('Received response with no content') 220 | return None 221 | except Exception as e: 222 | log.error(e) 223 | return None 224 | 225 | def delete(self, path, data=None, headers=None, params=None, trailing=None): 226 | """ 227 | Deletes resources at given paths. 228 | :rtype: dict 229 | :return: Empty dictionary to have consistent interface. 230 | Some of Atlassian REST resources don't return any content. 231 | """ 232 | response = self.request('DELETE', path=path, data=data, headers=headers, params=params, trailing=trailing) 233 | if self.advanced_mode: 234 | return response 235 | try: 236 | return response.json() 237 | except ValueError: 238 | log.debug('Received response with no content') 239 | return None 240 | except Exception as e: 241 | log.error(e) 242 | return None 243 | -------------------------------------------------------------------------------- /docs/jira.rst: -------------------------------------------------------------------------------- 1 | Jira module 2 | =========== 3 | 4 | Get issues from jql search result with all related fields 5 | --------------------------------------------------------- 6 | 7 | .. code-block:: python 8 | 9 | jql_request = 'project = DEMO AND status NOT IN (Closed, Resolved) ORDER BY issuekey' 10 | issues = jira.jql(jql_request) 11 | print(issues) 12 | 13 | Reindex Jira 14 | ------------ 15 | 16 | .. code-block:: python 17 | 18 | # Reindexing Jira 19 | jira.reindex() 20 | 21 | # Reindex status 22 | jira.reindex_status() 23 | 24 | # Reindex type 25 | jira.reindex_with_type(indexing_type="BACKGROUND_PREFERRED") 26 | """ 27 | FOREGROUND - runs a lock/full reindexing 28 | BACKGROUND - runs a background reindexing. 29 | If JIRA fails to finish the background reindexing, respond with 409 Conflict (error message). 30 | BACKGROUND_PREFERRED - If possible do a background reindexing. 31 | If it's not possible (due to an inconsistent index), do a foreground reindexing. 32 | """ 33 | 34 | Manage users 35 | ------------ 36 | 37 | .. code-block:: python 38 | 39 | # Get user 40 | jira.user(username) 41 | 42 | # Remove user 43 | jira.user_remove(username) 44 | 45 | # Deactivate user. Works from 8.3.0 release 46 | jira.user_deactivate(username) 47 | 48 | # Get web sudo cookies using normal http request 49 | jira.user_get_websudo() 50 | 51 | # Fuzzy search using username and display name 52 | jira.user_find_by_user_string(username, start=0, limit=50, include_inactive_users=False) 53 | 54 | Manage groups 55 | ------------- 56 | 57 | .. code-block:: python 58 | 59 | # Create a group 60 | jira.create_group(name) 61 | 62 | # Delete a group 63 | # If you delete a group and content is restricted to that group, the content will be hidden from all users 64 | # To prevent this, use this parameter to specify a different group to transfer the restrictions 65 | # (comments and worklogs only) to 66 | jira.remove_group(name, swap_group=None) 67 | 68 | # Get all users from group 69 | jira.get_all_users_from_group(group, include_inactive_users=False, start=0, limit=50) 70 | 71 | # Add given user to a group 72 | jira.add_user_to_group(username, group_name) 73 | 74 | # Remove given user from a group 75 | jira.remove_user_from_group(username, group_name) 76 | 77 | Manage projects 78 | --------------- 79 | 80 | .. code-block:: python 81 | 82 | # Get all projects 83 | # Returns all projects which are visible for the currently logged in user. 84 | jira.projects(included_archived=None) 85 | 86 | # Get all project alternative call 87 | # Returns all projects which are visible for the currently logged in user. 88 | jira.get_all_projects(included_archived=None) 89 | 90 | # Get project 91 | jira.project(key) 92 | 93 | # Get project components using project key 94 | jira.get_project_components(key) 95 | 96 | # Get a full representation of a the specified project's versions 97 | jira.get_project_versions(key, expand=None) 98 | 99 | # Returns all versions for the specified project. Results are paginated. 100 | # Results can be ordered by the following fields: sequence, name, startDate, releaseDate. 101 | jira.get_project_versions_paginated(key, start=None, limit=None, order_by=None, expand=None) 102 | 103 | # Add missing version to project 104 | jira.add_version(key, project_id, version, is_archived=False, is_released=False) 105 | 106 | # Get project leaders 107 | jira.project_leaders() 108 | 109 | # Get last project issuekey 110 | jira.get_project_issuekey_last(project) 111 | 112 | # Get all project issue keys 113 | jira.get_project_issuekey_all(project) 114 | 115 | # Get project issues count 116 | jira.get_project_issues_count(project) 117 | 118 | # Get all project issues 119 | jira.get_all_project_issues(project, fields='*all', start=100, limit=500) 120 | 121 | # Get all assignable users for project 122 | jira.get_all_assignable_users_for_project(project_key, start=0, limit=50) 123 | 124 | # Update a project 125 | jira.update_project(project_key, data, expand='lead,description') 126 | 127 | # Get project permission scheme 128 | # Use 'expand' to get details (default is None) 129 | jira.get_project_permission_scheme(project_id_or_key, expand='permissions,user,group,projectRole,field,all') 130 | 131 | # Get the issue security scheme for project. 132 | # Returned if the user has the administrator permission or if the scheme is used in a project in which the 133 | # user has the administrative permission. 134 | # Use only_levels=True for get the only levels entries 135 | jira.get_project_issue_security_scheme(project_id_or_key, only_levels=False) 136 | 137 | # Resource for associating notification schemes and projects. 138 | # Gets a notification scheme associated with the project. 139 | # Follow the documentation of /notificationscheme/{id} resource for all details about returned value. 140 | # Use 'expand' to get details (default is None) possible values are notificationSchemeEvents,user,group,projectRole,field,all 141 | jira.get_priority_scheme_of_project(project_key_or_id, expand=None) 142 | 143 | 144 | Manage issues 145 | ------------- 146 | 147 | .. code-block:: python 148 | 149 | # Get issue by key 150 | jira.issue(key) 151 | 152 | # Get issue field value 153 | jira.issue_field_value(key, field) 154 | 155 | # Update issue field 156 | fields = {'summary': 'New summary'} 157 | jira.update_issue_field(key, fields) 158 | 159 | # Get existing custom fields or find by filter 160 | get_custom_fields(self, search=None, start=1, limit=50): 161 | 162 | # Check issue exists 163 | jira.issue_exists(issue_key) 164 | 165 | # Check issue deleted 166 | jira.issue_deleted(issue_key) 167 | 168 | # Update issue 169 | jira.issue_update(issue_key, fields) 170 | 171 | # Create issue 172 | jira.issue_create(fields) 173 | 174 | # Issue create or update 175 | jira.issue_create_or_update(fields) 176 | 177 | # Get issue transitions 178 | jira.get_issue_transitions(issue_key) 179 | 180 | # Get status ID from name 181 | jira.get_status_id_from_name(status_name) 182 | 183 | # Get transition id to status name 184 | jira.get_transition_id_to_status_name(issue_key, status_name) 185 | 186 | # Transition issue 187 | jira.issue_transition(issue_key, status) 188 | 189 | # Set issue status 190 | jira.set_issue_status(issue_key, status_name, fields=None) 191 | 192 | # Set issue status by transition_id 193 | jira.set_issue_status_by_transition_id(issue_key, transition_id) 194 | 195 | # Get issue status 196 | jira.get_issue_status(issue_key) 197 | 198 | # Create or Update Issue Links 199 | jira.create_or_update_issue_remote_links(issue_key, link_url, title, global_id=None, relationship=None) 200 | 201 | # Get Issue Link by link ID 202 | jira.get_issue_remote_link_by_id(issue_key, link_id) 203 | 204 | # Update Issue Link by link ID 205 | jira.update_issue_remote_link_by_id(issue_key, link_id, url, title, global_id=None, relationship=None) 206 | 207 | # Delete Issue Links 208 | jira.delete_issue_remote_link_by_id(issue_key, link_id) 209 | 210 | 211 | Manage Boards 212 | ------------- 213 | 214 | .. code-block:: python 215 | 216 | # Create sprint 217 | jira.jira.create_sprint(sprint_name, origin_board_id, start_datetime, end_datetime, goal) 218 | 219 | # Rename sprint 220 | jira.rename_sprint(sprint_id, name, start_date, end_date) 221 | 222 | # Add/Move Issues to sprint 223 | jira.add_issues_to_sprint(sprint_id, issues_list) 224 | 225 | 226 | Attachments actions 227 | ------------------- 228 | 229 | .. code-block:: python 230 | 231 | # Add attachment to issue 232 | jira.add_attachment(issue_key, filename) 233 | 234 | Manage components 235 | ----------------- 236 | 237 | .. code-block:: python 238 | 239 | # Get component 240 | jira.component(component_id) 241 | 242 | # Create component 243 | jira.create_component(component) 244 | 245 | # Delete component 246 | jira.delete_component(component_id) 247 | 248 | Upload Jira plugin 249 | ------------------ 250 | 251 | .. code-block:: python 252 | 253 | upload_plugin(plugin_path) 254 | 255 | Issue link types 256 | ---------------- 257 | .. code-block:: python 258 | 259 | # Get Issue link types 260 | jira.get_issue_link_types(): 261 | 262 | # Create Issue link types 263 | jira.create_issue_link_type(data): 264 | """Create a new issue link type. 265 | :param data: 266 | { 267 | "name": "Duplicate", 268 | "inward": "Duplicated by", 269 | "outward": "Duplicates" 270 | } 271 | 272 | """ 273 | 274 | # Get issue link type by id 275 | jira.get_issue_link_type(issue_link_type_id): 276 | 277 | # Delete issue link type 278 | jira.delete_issue_link_type(issue_link_type_id): 279 | 280 | # Update issue link type 281 | jira.update_issue_link_type(issue_link_type_id, data): 282 | 283 | Issue security schemes 284 | ---------------------- 285 | .. code-block:: python 286 | 287 | # Get all security schemes. 288 | # Returned if the user has the administrator permission or if the scheme is used in a project in which the 289 | # user has the administrative permission. 290 | jira.get_issue_security_schemes() 291 | 292 | # Get issue security scheme. 293 | # Returned if the user has the administrator permission or if the scheme is used in a project in which the 294 | # user has the administrative permission. 295 | # Use only_levels=True for get the only levels entries 296 | jira.get_issue_security_scheme(scheme_id, only_levels=False) 297 | 298 | TEMPO 299 | ---------------------- 300 | .. code-block:: python 301 | 302 | # Find existing worklogs with the search parameters. 303 | # Look at the tempo docs for additional information: 304 | # https://www.tempo.io/server-api-documentation/timesheets#operation/searchWorklogs 305 | # NOTE: check if you are using correct types for the parameters! 306 | # :param from: string From Date 307 | # :param to: string To Date 308 | # :param worker: Array of strings 309 | # :param taskId: Array of integers 310 | # :param taskKey: Array of strings 311 | # :param projectId: Array of integers 312 | # :param projectKey: Array of strings 313 | # :param teamId: Array of integers 314 | # :param roleId: Array of integers 315 | # :param accountId: Array of integers 316 | # :param accountKey: Array of strings 317 | # :param filterId: Array of integers 318 | # :param customerId: Array of integers 319 | # :param categoryId: Array of integers 320 | # :param categoryTypeId: Array of integers 321 | # :param epicKey: Array of strings 322 | # :param updatedFrom: string 323 | # :param includeSubtasks: boolean 324 | # :param pageNo: integer 325 | # :param maxResults: integer 326 | # :param offset: integer 327 | jira.tempo_4_timesheets_find_worklogs(**params) 328 | 329 | # :PRIVATE: 330 | # Get Tempo timesheet worklog by issue key or id. 331 | jira.tempo_timesheets_get_worklogs_by_issue(issue) 332 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2014 Mateusz Harasymczuk 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /atlassian/utils.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import logging 3 | import re 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | 8 | def is_email(element): 9 | """ 10 | >>> is_email('username@example.com') 11 | True 12 | >>> is_email('example.com') 13 | False 14 | >>> is_email('firstname.lastname@domain.co.uk') 15 | True 16 | """ 17 | email_regex = r'^[A-Za-z0-9\.\+_-]+@[A-Za-z0-9\._-]+\.[a-zA-Z]*$' 18 | return re.match(email_regex, str(element)) 19 | 20 | 21 | def html_email(email, title=None): 22 | """ 23 | >>> html_email('username@example.com') 24 | 'username@example.com' 25 | """ 26 | 27 | if not title: 28 | title = email 29 | 30 | return '{title}'.format(email=email, title=title) 31 | 32 | 33 | def html_list(data): 34 | """ 35 | >>> html_list(['example.com', 'admin1@example.com', 'admin2@example.com']) 36 | '' 41 | """ 42 | html = '' 58 | 59 | 60 | def html_table_header_row(data): 61 | """ 62 | >>> html_table_header_row(['administrators', 'key', 'leader', 'project']) 63 | '\\n\\tAdministratorsKeyLeaderProject' 64 | >>> html_table_header_row(['key', 'project', 'leader', 'administrators']) 65 | '\\n\\tKeyProjectLeaderAdministrators' 66 | """ 67 | html = '\n\t' 68 | 69 | for th in data: 70 | title = th.replace('_', ' ').title() 71 | html += '{}'.format(title) 72 | 73 | return html + '' 74 | 75 | 76 | def html_row_with_ordered_headers(data, col_headers, row_header=None): 77 | """ 78 | >>> headers = ['administrators', 'key', 'leader', 'project'] 79 | >>> data = {'key': 'DEMO', 'project': 'Demonstration', 80 | 'leader': 'leader@example.com', 81 | 'administrators': ['admin1@example.com', 'admin2@example.com']} 82 | >>> html_row_with_ordered_headers(data, headers) 83 | '\\n\\tDEMOleader@example.comDemonstration' 87 | >>> headers = ['key', 'project', 'leader', 'administrators'] 88 | >>> html_row_with_ordered_headers(data, headers) 89 | '\\n\\tDEMODemonstration 90 | leader@example.com 91 | ' 95 | """ 96 | html = '\n\t' 97 | 98 | if row_header: 99 | html += '{}'.format(row_header.replace('_', ' ').title()) 100 | for header in col_headers: 101 | element = data[header] 102 | 103 | if isinstance(element, list): 104 | element = html_list(element) 105 | 106 | if is_email(element): 107 | element = html_email(element) 108 | 109 | html += '{}'.format(element) 110 | 111 | return html + '' 112 | 113 | 114 | def html_table_from_dict(data, ordering): 115 | """ 116 | >>> ordering = ['administrators', 'key', 'leader', 'project'] 117 | >>> data = [ \ 118 | {'key': 'DEMO', 'project': 'Demo project', 'leader': 'lead@example.com', \ 119 | 'administrators': ['admin@example.com', 'root@example.com']},] 120 | >>> html_table_from_dict(data, ordering) 121 | ' 122 | \\n 123 | 124 | 125 | 126 | 127 | 128 | \\n 129 | 130 | 136 | 137 | 138 | 139 | \\n 140 | 141 |
AdministratorsKeyLeaderProject
131 | 135 | DEMOlead@example.comDemo project
' 142 | >>> ordering = ['key', 'project', 'leader', 'administrators'] 143 | >>> html_table_from_dict(data, ordering) 144 | ' 145 | \\n 146 | 147 | 148 | 149 | 150 | 151 | \\n 152 | 153 | 154 | 155 | 156 | 162 | \\n 163 | 164 |
KeyProjectLeaderAdministrators
DEMODemo projectlead@example.com 157 | 161 |
' 165 | """ 166 | html = '' 167 | html += html_table_header_row(ordering) 168 | 169 | for row in data: 170 | html += html_row_with_ordered_headers(row, ordering) 171 | 172 | return html + '\n
' 173 | 174 | 175 | def html_table_from_nested_dict(data, ordering): 176 | """ 177 | >>> ordering = ['manager', 'admin', 'employee_count'] 178 | >>> data = { 179 | 'project_A': {'manager': 'John', 'admin': 'admin1@example.com', 'employee_count': '4'}, 180 | 'project_B': {'manager': 'Jane', 'admin': 'admin2@example.com', 'employee_count': '7'} 181 | } 182 | >>> html_table_from_nested_dict(data, ordering) 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 |
ManagerAdminEmployee Count
Project AJohnadmin1@example.com4
Project BJaneadmin2@example.com7
205 | """ 206 | 207 | html = '' 208 | # Add an empty first cell for the row header column 209 | header_row = [''] 210 | header_row.extend(ordering) 211 | html += html_table_header_row(header_row) 212 | 213 | for row_header, row in data.items(): 214 | html += html_row_with_ordered_headers(row, ordering, row_header) 215 | 216 | return html + '\n
' 217 | 218 | 219 | def block_code_macro_confluence(code, lang=None): 220 | """ 221 | Wrap into code block macro 222 | :param code: 223 | :param lang: 224 | :return: 225 | """ 226 | if not lang: 227 | lang = '' 228 | return ('''\ 229 | 230 | {lang} 231 | 232 | 233 | ''').format(lang=lang, code=code) 234 | 235 | 236 | def html_code__macro_confluence(text): 237 | """ 238 | Wrap into html macro 239 | :param text: 240 | :return: 241 | """ 242 | return ('''\ 243 | 244 | 245 | 246 | ''').format(text=text) 247 | 248 | 249 | def noformat_code_macro_confluence(text, nopanel=None): 250 | """ 251 | Wrap into code block macro 252 | :param text: 253 | :param nopanel: (bool) True or False Removes the panel around the content. 254 | :return: 255 | """ 256 | if not nopanel: 257 | nopanel = False 258 | return ('''\ 259 | 260 | {nopanel} 261 | 262 | 263 | ''').format(nopanel=nopanel, text=text) 264 | 265 | 266 | def symbol_normalizer(text): 267 | if not text: 268 | return "" 269 | result = text 270 | result = result.replace('Ä', u'Ä') 271 | result = result.replace('ä', u'ä') 272 | result = result.replace('Ë', u'Ë') 273 | result = result.replace('ë', u'ë') 274 | result = result.replace('Ï', u'Ï') 275 | result = result.replace('ï', u'ï') 276 | result = result.replace('Ö', u'Ö') 277 | result = result.replace('ö', u'ö') 278 | result = result.replace('Ü', u'Ü') 279 | result = result.replace('ü', u'ü') 280 | result = result.replace('Á', u'Á') 281 | result = result.replace('á', u'á') 282 | result = result.replace('É', u'É') 283 | result = result.replace('é', u'é') 284 | result = result.replace('Í', u'Í') 285 | result = result.replace('í', u'í') 286 | result = result.replace('Ó', u'Ó') 287 | result = result.replace('ó', u'ó') 288 | result = result.replace('Ú', u'Ú') 289 | result = result.replace('ú', u'ú') 290 | result = result.replace('À', u'À') 291 | result = result.replace('à', u'à') 292 | result = result.replace('È', u'È') 293 | result = result.replace('è', u'è') 294 | result = result.replace('Ì', u'Ì') 295 | result = result.replace('ì', u'ì') 296 | result = result.replace('Ò', u'Ò') 297 | result = result.replace('ò', u'ò') 298 | result = result.replace('Ù', u'Ù') 299 | result = result.replace('ù', u'ù') 300 | result = result.replace('Â', u'Â') 301 | result = result.replace('â', u'â') 302 | result = result.replace('Ê', u'Ê') 303 | result = result.replace('ê', u'ê') 304 | result = result.replace('Î', u'Î') 305 | result = result.replace('î', u'î') 306 | result = result.replace('Ô', u'Ô') 307 | result = result.replace('ô', u'ô') 308 | result = result.replace('Û', u'Û') 309 | result = result.replace('û', u'û') 310 | result = result.replace('Å', u'Å') 311 | result = result.replace('å', u'å') 312 | result = result.replace('°', u'°') 313 | return result 314 | 315 | 316 | def parse_cookie_file(cookie_file): 317 | """ 318 | Parse a cookies.txt file (Netscape HTTP Cookie File) 319 | return a dictionary of key value pairs 320 | compatible with requests. 321 | :param cookie_file: a cookie file 322 | :return dict of cookies pair 323 | """ 324 | cookies = {} 325 | with open(cookie_file, 'r') as fp: 326 | for line in fp: 327 | if not re.match(r'^#', line): 328 | line_fields = line.strip().split('\t') 329 | cookies[line_fields[5]] = line_fields[6] 330 | return cookies 331 | -------------------------------------------------------------------------------- /docs/bitbucket.rst: -------------------------------------------------------------------------------- 1 | BitBucket module 2 | ================ 3 | 4 | Manage projects 5 | --------------- 6 | 7 | .. code-block:: python 8 | 9 | # Project list 10 | bitbucket.project_list() 11 | 12 | # Repo list 13 | bitbucket.repo_list(project_key) 14 | 15 | # Project info 16 | bitbucket.project(key) 17 | 18 | # Create project 19 | bitbucket.create_project(key, name, description="My pretty project") 20 | 21 | # Get users who has permission in project 22 | bitbucket.project_users(key, limit=99999, filter_str=None) 23 | 24 | # Get project administrators for project 25 | butbucket.project_users_with_administrator_permissions(key) 26 | 27 | # Get Project Groups 28 | bitbucket.project_groups(key, limit=99999, filter_str=None) 29 | 30 | # Get groups with admin permissions 31 | bitbucket.project_groups_with_administrator_permissions(key) 32 | 33 | # Project summary 34 | bitbucket.project_summary(key) 35 | 36 | # Grant project permission to an specific user 37 | bitbucket.project_grant_user_permissions(project_key, username, permission) 38 | 39 | # Grant project permission to an specific group 40 | bitbucket.project_grant_group_permissions(project_key, groupname, permission) 41 | 42 | Manage repositories 43 | ------------------- 44 | 45 | .. code-block:: python 46 | 47 | # Get single repository 48 | bitbucket.get_repo(project_key, repository_slug) 49 | 50 | # Get labels for a single repository 51 | bitbucket.get_repo_labels(project_key, repository_slug) 52 | 53 | # Set label for a single repository 54 | bitbucket.set_repo_label(project_key, repository_slug, label_name) 55 | 56 | # Disable branching model 57 | bitbucket.disable_branching_model(project_key, repo_key) 58 | 59 | # Enable branching model 60 | bitbucket.enable_branching_model(project_key, repo_key) 61 | 62 | # Get branching model 63 | bitbucket.get_branching_model(project_key, repo_key) 64 | 65 | # Set branching model 66 | data = {'development': {'refId': None, 'useDefault': True}, 67 | 'types': [{'displayName': 'Bugfix', 68 | 'enabled': True, 69 | 'id': 'BUGFIX', 70 | 'prefix': 'bugfix-'}, 71 | {'displayName': 'Feature', 72 | 'enabled': True, 73 | 'id': 'FEATURE', 74 | 'prefix': 'feature-'}, 75 | {'displayName': 'Hotfix', 76 | 'enabled': True, 77 | 'id': 'HOTFIX', 78 | 'prefix': 'hotfix-'}, 79 | {'displayName': 'Release', 80 | 'enabled': True, 81 | 'id': 'RELEASE', 82 | 'prefix': 'release/'}]} 83 | bitbucket.set_branching_model(project_key, repo_key, data) 84 | 85 | bitbucket.repo_users(project_key, repo_key, limit=99999, filter_str=None) 86 | 87 | bitbucket.repo_groups(project_key, repo_key, limit=99999, filter_str=None) 88 | 89 | # Grant repository permission to an specific user 90 | bitbucket.repo_grant_user_permissions(project_key, repo_key, username, permission) 91 | 92 | # Grant repository permission to an specific group 93 | bitbucket.repo_grant_group_permissions(project_key, repo_key, groupname, permission) 94 | 95 | # Delete a repository (DANGER!) 96 | bitbucket.delete_repo(project_key, repository_slug) 97 | 98 | Groups and admins 99 | ----------------- 100 | 101 | .. code-block:: python 102 | 103 | # Get group of members 104 | bitbucket.group_members(group, limit=99999) 105 | 106 | # All project administrators 107 | bitbucket.all_project_administrators() 108 | 109 | # Get users. Use 'user_filter' parameter to get specific users. 110 | bitbucket.get_users(user_filter="username") 111 | 112 | Manage code 113 | ----------- 114 | 115 | .. code-block:: python 116 | 117 | # Get repositories list from project 118 | bitbucket.repo_list(project_key, limit=25) 119 | 120 | # Create a new repository. 121 | # Requires an existing project in which this repository will be created. The only parameters which will be used 122 | # are name and scmId. 123 | # The authenticated user must have PROJECT_ADMIN permission for the context project to call this resource. 124 | bitbucket.create_repo(project_key, repository, forkable=False, is_private=True) 125 | 126 | # Get branches from repo 127 | bitbucket.get_branches(project, repository, filter='', limit=99999, details=True) 128 | 129 | # Creates a branch using the information provided in the request. 130 | # The authenticated user must have REPO_WRITE permission for the context repository to call this resource. 131 | bitbucket.create_branch(project_key, repository, name, start_point, message) 132 | 133 | # Delete branch from related repo 134 | bitbucket.delete_branch(project, repository, name, end_point) 135 | 136 | # Get pull requests 137 | bitbucket.get_pull_requests(project, repository, state='OPEN', order='newest', limit=100, start=0) 138 | 139 | # Get pull request activities 140 | bitbucket.get_pull_requests_activities(project, repository, pull_request_id) 141 | 142 | # Get pull request changes 143 | bitbucket.get_pull_requests_changes(project, repository, pull_request_id) 144 | 145 | # Get pull request commits 146 | bitbucket.get_pull_requests_commits(project, repository, pull_request_id) 147 | 148 | # Add comment into pull request 149 | bitbucket.add_pull_request_comment(project, repository, pull_request_id, text) 150 | 151 | # Create a new pull request between two branches. 152 | bitbucket.open_pull_request(source_project, source_repo, dest_project, dest_repo, source_branch, destination_branch, title, description) 153 | 154 | # Create a new pull request between two branches with one reviewer 155 | bitbucket.open_pull_request(source_project, source_repo, dest_project, dest_repo, source_branch, destination_branch, title, description, reviewers='name') 156 | 157 | # Create a new pull request between two branches with multiple reviewers. 158 | bitbucket.open_pull_request(source_project, source_repo, dest_project, dest_repo, source_branch, destination_branch, title, description, reviewers=['name1', 'name2']) 159 | 160 | # Delete a pull request 161 | bitbucket.delete_pull_request(project, repository, pull_request_id, pull_request_version) 162 | 163 | # Get tags for related repo 164 | bitbucket.get_tags(project, repository, filter='', limit=99999) 165 | 166 | # Get project tags 167 | # The authenticated user must have REPO_READ permission for the context repository to call this resource 168 | bitbucket.get_project_tags(project, repository, tag_name) 169 | 170 | # Set tag 171 | # The authenticated user must have REPO_WRITE permission for the context repository to call this resource 172 | bitbucket.set_tag(project, repository, tag_name, commit_revision, description=None) 173 | 174 | # Delete tag 175 | # The authenticated user must have REPO_WRITE permission for the context repository to call this resource 176 | bitbucket.delete_tag(project, repository, tag_name) 177 | 178 | # Get diff 179 | bitbucket.get_diff(project, repository, path, hash_oldest, hash_newest) 180 | 181 | # Get commit list from repo 182 | bitbucket.get_commits(project, repository, hash_oldest, hash_newest, limit=99999) 183 | 184 | # Get change log between 2 refs 185 | bitbucket.get_changelog(project, repository, ref_from, ref_to, limit=99999) 186 | 187 | # Get raw content of the file from repo 188 | bitbucket.get_content_of_file(project, repository, filename, at=None, markup=None) 189 | """ 190 | Retrieve the raw content for a file path at a specified revision. 191 | The authenticated user must have REPO_READ permission for the specified repository to call this resource. 192 | """ 193 | 194 | Branch permissions 195 | ------------------ 196 | 197 | .. code-block:: python 198 | 199 | # Set branches permissions 200 | bitbucket.set_branches_permissions(project_key, multiple_permissions=False, matcher_type=None, matcher_value=None, permission_type=None, repository=None, except_users=[], except_groups=[], except_access_keys=[], start=0, limit=25) 201 | 202 | # Delete a single branch permission by premission id 203 | bitbucket.delete_branch_permission(project_key, permission_id, repository=None) 204 | 205 | # Get a single branch permission by permission id 206 | bitbucket.get_branch_permission(project_key, permission_id, repository=None) 207 | 208 | Pull Request management 209 | ----------------------- 210 | 211 | .. code-block:: python 212 | 213 | # Decline pull request 214 | bitbucket.decline_pull_request(project_key, repository, pr_id, pr_version) 215 | 216 | # Check if pull request can be merged 217 | bitbucket.is_pull_request_can_be_merged(project_key, repository, pr_id) 218 | 219 | # Merge pull request 220 | bitbucket.merge_pull_request(project_key, repository, pr_id, pr_version) 221 | 222 | # Reopen pull request 223 | bitbucket.reopen_pull_request(project_key, repository, pr_id, pr_version) 224 | 225 | Conditions-Reviewers management 226 | ------------------------------- 227 | 228 | .. code-block:: python 229 | 230 | # Get all project conditions with reviewers list for specific project 231 | bitbucket.get_project_conditions(project_key) 232 | 233 | # Get a project condition with reviewers list for specific project 234 | bitbucket.get_project_condition(project_key, id_condition) 235 | 236 | # Create project condition with reviewers for specific project 237 | # :example condition: '{"sourceMatcher":{"id":"any","type":{"id":"ANY_REF"}},"targetMatcher":{"id":"refs/heads/master","type":{"id":"BRANCH"}},"reviewers":[{"id": 12}],"requiredApprovals":"0"}' 238 | bitbucket.create_project_condition(project_key, condition) 239 | 240 | # Update a project condition with reviewers for specific project 241 | # :example condition: '{"sourceMatcher":{"id":"any","type":{"id":"ANY_REF"}},"targetMatcher":{"id":"refs/heads/master","type":{"id":"BRANCH"}},"reviewers":[{"id": 12}],"requiredApprovals":"0"}' 242 | bitbucket.update_project_condition(project_key, condition, id_condition) 243 | 244 | # Delete a project condition for specific project 245 | bitbucket.delete_project_condition(project_key, id_condition) 246 | 247 | # Get all repository conditions with reviewers list for specific repository in project 248 | bitbucket.get_repo_conditions(project_key, repo_key) 249 | 250 | # Get repository conditions with reviewers list only only conditions type PROJECT for specific repository in project 251 | bitbucket.get_repo_project_conditions(project_key, repo_key) 252 | 253 | # Get repository conditions with reviewers list only conditions type REPOSITORY for specific repository in project 254 | bitbucket.get_repo_repo_conditions(project_key, repo_key) 255 | 256 | # Get a project condition with reviewers list for specific repository in project 257 | bitbucket.get_repo_condition(project_key, repo_key, id_condition) 258 | 259 | # Create project condition with reviewers for specific repository in project 260 | # :example condition: '{"sourceMatcher":{"id":"any","type":{"id":"ANY_REF"}},"targetMatcher":{"id":"refs/heads/master","type":{"id":"BRANCH"}},"reviewers":[{"id": 12}],"requiredApprovals":"0"}' 261 | bitbucket.create_repo_condition(project_key, repo_key, condition) 262 | 263 | # Update a project condition with reviewers for specific repository in project 264 | # :example condition: '{"sourceMatcher":{"id":"any","type":{"id":"ANY_REF"}},"targetMatcher":{"id":"refs/heads/master","type":{"id":"BRANCH"}},"reviewers":[{"id": 12}],"requiredApprovals":"0"}' 265 | bitbucket.update_repo_condition(project_key, repo_key, condition, id_condition) 266 | 267 | # Delete a project condition for specific repository in project 268 | bitbucket.delete_repo_condition(project_key, repo_key, id_condition) 269 | 270 | Pipelines management 271 | -------------------- 272 | 273 | .. code-block:: python 274 | 275 | # Get most recent Pipelines results for repository 276 | bitbucket.get_pipelines(workspace, repository) 277 | 278 | # Trigger default Pipeline on the latest revision of the master branch 279 | bitbucket.trigger_pipeline(workspace, repository) 280 | 281 | # Trigger default Pipeline on the latest revision of the develop branch 282 | bitbucket.trigger_pipeline(workspace, repository, branch="develop") 283 | 284 | # Trigger default Pipeline on a specific revision of the develop branch 285 | bitbucket.trigger_pipeline(workspace, repository, branch="develop", revision="<40-char hash>") 286 | 287 | # Trigger specific Pipeline on a specific revision of the master branch 288 | bitbucket.trigger_pipeline(workspace, repository, revision="<40-char hash>", name="style-check") 289 | 290 | # Get specific Pipeline by UUID 291 | bitbucket.get_pipeline(workspace, repository, "{7d6c327d-6336-4721-bfeb-c24caf25045c}") 292 | 293 | # Stop specific Pipeline by UUID 294 | bitbucket.stop_pipeline(workspace, repository, "{7d6c327d-6336-4721-bfeb-c24caf25045c}") 295 | 296 | # Get steps of Pipeline specified by UUID 297 | bitbucket.get_pipeline_steps(workspace, repository, "{7d6c327d-6336-4721-bfeb-c24caf25045c}") 298 | 299 | # Get step of Pipeline specified by UUIDs 300 | bitbucket.get_pipeline_step(workspace, repository, "{7d6c327d-6336-4721-bfeb-c24caf25045c}", "{56d2d8af-6526-4813-a22c-733ec6ecabf3}") 301 | 302 | # Get log of step of Pipeline specified by UUIDs 303 | bitbucket.get_pipeline_step_log(workspace, repository, "{7d6c327d-6336-4721-bfeb-c24caf25045c}", "{56d2d8af-6526-4813-a22c-733ec6ecabf3}") 304 | 305 | Manage issues 306 | ------------- 307 | 308 | .. code-block:: python 309 | 310 | # Get all tracked issues 311 | bitbucket.get_issues(workspace, repository) 312 | 313 | # Get all unassigned issues and sort them by priority 314 | bitbucket.get_issues(workspace, repository, sort_by="priority", query='assignee = null') 315 | 316 | # Create a new issue 317 | bitbucket.create_issue(workspace, repository, "The title", "The description") 318 | 319 | # Create a new issue of kind 'enhancement' and priority 'minor' 320 | bitbucket.create_issue(workspace, repository, "New idea", "How about this", kind="enhancement", priority="minor") 321 | 322 | # Update the 'priority' field of the issue 42 323 | bitbucket.update_issue(workspace, repository, 42, priority="blocker") 324 | 325 | # Mark issue 42 as resolved 326 | bitbucket.update_issue(workspace, repository, 42, state="resolved") 327 | 328 | # Get information about issue 1 329 | bitbucket.get_issue(workspace, repository, 1) 330 | 331 | # Delete issue 123 332 | bitbucket.delete_issue(workspace, repository, 123) 333 | --------------------------------------------------------------------------------