├── 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| ITEM | VALUE |
\n'
8 | html_data += '\t| key | {key} |
\n'.format(**project)
9 | html_data += '\t| name | {name} |
\n'.format(**project)
10 | html_data += '\t| description | {description} |
\n'.format(**project)
11 | html_data += '\t| id | {id} |
\n'.format(**project)
12 | return html_data + '
\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 | | Project Key |
12 | Project Name |
13 | Leader |
14 | Email |
15 |
"""
16 |
17 | for data in jira.project_leaders():
18 | html += """
19 | | {project_key} |
20 | {project_name} |
21 | {lead_name} |
22 | {lead_email} |
23 |
""".format(**data)
24 |
25 | html += '
'
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 = '| Project Key | Project Name | Administrator |
'
16 |
17 | for data in bitbucket.all_project_administrators():
18 | html += '| {project_key} | {project_name} | '.format(**data)
19 | for user in data['project_administrators']:
20 | html += '- {name}
'.format(**user)
21 | html += ' |
'
22 |
23 | 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 | | Project Key |
23 | Project Name |
24 | Leader |
25 | Email |
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 | | {project_key} |
33 | {project_name} |
34 | {lead_name} |
35 | {lead_email} |
36 |
"""
37 | html.append(row.format(**data))
38 |
39 | html.append('
')
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 = ''
43 |
44 | for item in data:
45 |
46 | if isinstance(item, dict):
47 | if item.get('email'):
48 | item = html_email(item.get('email'), item.get('name', None))
49 | elif item.get('name'):
50 | item = item.get('name')
51 |
52 | if is_email(item):
53 | item = html_email(item, item)
54 |
55 | html += '- {}
'.format(item)
56 |
57 | return html + '
'
58 |
59 |
60 | def html_table_header_row(data):
61 | """
62 | >>> html_table_header_row(['administrators', 'key', 'leader', 'project'])
63 | '\\n\\t| Administrators | Key | Leader | Project |
'
64 | >>> html_table_header_row(['key', 'project', 'leader', 'administrators'])
65 | '\\n\\t| Key | Project | Leader | Administrators |
'
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\\t | DEMO | leader@example.com | Demonstration |
'
87 | >>> headers = ['key', 'project', 'leader', 'administrators']
88 | >>> html_row_with_ordered_headers(data, headers)
89 | '\\n\\t| DEMO | Demonstration |
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 | | Administrators |
125 | Key |
126 | Leader |
127 | Project |
128 |
\\n
129 |
130 | |
131 |
135 | |
136 | DEMO |
137 | lead@example.com |
138 | Demo project |
139 |
\\n
140 |
141 |
'
142 | >>> ordering = ['key', 'project', 'leader', 'administrators']
143 | >>> html_table_from_dict(data, ordering)
144 | '
145 | \\n
146 |
147 | | Key |
148 | Project |
149 | Leader |
150 | Administrators |
151 |
\\n
152 |
153 | | DEMO |
154 | Demo project |
155 | lead@example.com |
156 |
157 |
161 | |
162 |
\\n
163 |
164 |
'
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 | Manager |
188 | Admin |
189 | Employee Count |
190 |
191 |
192 | | Project A |
193 | John |
194 | admin1@example.com |
195 | 4 |
196 |
197 |
198 | | Project B |
199 | Jane |
200 | admin2@example.com |
201 | 7 |
202 |
203 |
204 |
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 |
--------------------------------------------------------------------------------