Skip to content

Commit

Permalink
Merge branch 'develop' into 'master'
Browse files Browse the repository at this point in the history
Develop

See merge request !2
  • Loading branch information
Alexey Burov committed Jul 12, 2017
2 parents e50a98f + 3b8737a commit 2d61885
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 29 deletions.
104 changes: 102 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,103 @@
*.pyc
.idea/*
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# dotenv
.env

# virtualenv
.venv
venv/
ENV/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/

test.py
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
48 changes: 29 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
### TFS Python Library (TFS API Python client).
## Quickstart
### Workitem get and update field
### Create connection
```python
from tfs import TFSAPI

user="username"
password="password"

client = TFSAPI("https://tfs.tfs.ru/tfs/", project="Development", user=user, password=password)
workitem = client.get_workitem(100) # Test connection with Workitem id

```
### Workitem
```python
workitem = client.get_workitem(100)

# Case insensetive. Remove space in field name
Expand All @@ -17,28 +22,41 @@ print(workitem['assignedTo'])
workitem['state'] = 'Complete'

# Add comment
print(workitem.history)
workitem['History'] = "Omg, it is goos issue!"

print(workitem.history)

```
### Changesets and relation Workitem
```python
from tfs import TFSAPI
# Workitem Parent Workitem
parent = workitem.parent
if parent: # Parent is None if Workitem hasn't Parent link
print("Workitem with id={} have parent={}".format(workitem.id, parent.id))

user="username"
password="password"

client = TFSAPI("https://tfs.tfs.ru/tfs/", project="Development", user=user, password=password)
# Workitem Childs Workitem
childs = workitem.childs
if childs: # Child is empty list if Workitem hasn't Child link
print("Workitem with id={} have Childs={}".format(workitem.id, ",".join([x.id for x in childs])))
```

### Changesets
```python
# Get changesets from 1000 to 1002
changesets = client.get_changesets(from_=1000, to_=1002)

# Get changesets and related Workitems
changesets = client.get_changesets(top=1)
linked_workitems = changesets[0].workitems
```

### Project & Team
```python
# Get all project
all_projects = client.get_projects()

# Get project
project_name = client.get_project("MyProjectName")

# Get project team
project_team = project_name.team
```

## Installation
Expand All @@ -47,15 +65,7 @@ pip install dohq-tfs
```

## Guide
### Supported action:
- **Workitem**:
- Get info about **Workitem**
- Set field
- **Changeset**
- Get info about **Changeset**
- Get relation workitems

### Tested Compability:
### Tested compability:
- TFS 2015

## Development
Expand Down
1 change: 1 addition & 0 deletions _config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
theme: jekyll-theme-minimal
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"count": 1,
"value": [
{
"id": "26e73aff-d80b-430d-b95c-76140bf184de",
"name": "ProjectName",
"url": "https:\/\/tfs.tfs.ru\/tfs\/Development\/_apis\/projects\/26e73aff-d80b-430d-b95c-76140bf184de",
"state": "wellFormed",
"revision": 43900539
}
]
}
7 changes: 7 additions & 0 deletions tests/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ def test_get_projects(tfsapi):
assert len(projects) == 1
assert projects[0]['name'] == 'ProjectName'

@pytest.mark.httpretty
def test_get_project(tfsapi):
projects = tfsapi.get_project('ProjectName')

assert len(projects) == 1
assert projects[0]['name'] == 'ProjectName'


@pytest.mark.httpretty
def test_get_teams(tfsapi):
Expand Down
69 changes: 67 additions & 2 deletions tests/test_tfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
import httpretty
import pytest

from tfs.tfs import *
from tfs.resources import *


class TestWorkitem(object):
@pytest.fixture()
def workitem(self, tfsapi):
def workitem_with_child_only(self, tfsapi):
data_str = r"""{
"id": 100,
"rev": 1,
Expand All @@ -31,12 +31,67 @@ def workitem(self, tfsapi):
"Microsoft.VSTS.Common.Priority": 2,
"Microsoft.VSTS.Common.Severity": "3 - Medium"
},
"relations": [
{
"rel": "System.LinkTypes.Hierarchy-Forward",
"url": "https:\/\/tfs.tfs.ru\/tfs\/Development\/_apis\/wit\/workItems\/10",
"attributes": {
"isLocked": false
}
},
{
"rel": "System.LinkTypes.Hierarchy-Forward",
"url": "https:\/\/tfs.tfs.ru\/tfs\/Development\/_apis\/wit\/workItems\/11",
"attributes": {
"isLocked": false
}
}
],
"url": "https:\/\/tfs.tfs.ru\/tfs\/Development\/_apis\/wit\/workItems\/100"
}"""
data_ = json.loads(data_str)
wi = Workitem(data_, tfsapi)
yield wi

@pytest.fixture()
def workitem(self, tfsapi):
data_str = r"""{
"id": 100,
"rev": 1,
"fields": {
"System.AreaPath": "Test Agile",
"System.TeamProject": "Test Agile",
"System.IterationPath": "Test Agile\\Current\\Iteration 1",
"System.WorkItemType": "Bug",
"System.State": "Active",
"System.Reason": "New",
"System.CreatedDate": "2015-10-14T07:40:46.96Z",
"System.CreatedBy": "Alexey Ivanov <DOMAIN\\AIvanov>",
"System.ChangedDate": "2015-10-14T07:40:46.96Z",
"System.ChangedBy": "Alexey Ivanov <DOMAIN\\AIvanov>",
"System.Title": "\u044c\u0441\u0440\u0442\u043e",
"Microsoft.VSTS.Common.StateChangeDate": "2015-10-14T07:40:46.96Z",
"Microsoft.VSTS.Common.ActivatedDate": "2015-10-14T07:40:46.96Z",
"Microsoft.VSTS.Common.ActivatedBy": "Alexey Ivanov <DOMAIN\\AIvanov>",
"Microsoft.VSTS.Common.Priority": 2,
"Microsoft.VSTS.Common.Severity": "3 - Medium",
"Custom.Bug.Type": "Manual Test Case"
},
"url": "https:\/\/tfs.tfs.ru\/tfs\/Development\/_apis\/wit\/workItems\/100",
"relations": [
{
"rel": "System.LinkTypes.Hierarchy-Reverse",
"url": "https:\/\/tfs.tfs.ru\/tfs\/Development\/_apis\/wit\/workItems\/110",
"attributes": {
"isLocked": false
}
}
]
}"""
data_ = json.loads(data_str)
wi = Workitem(data_, tfsapi)
yield wi

def test_workitem_id(self, workitem):
assert workitem.id == 100

Expand All @@ -48,6 +103,9 @@ def test_workitem_fields_with_prefix(self, workitem):
assert workitem['System.Reason'] == "New"
assert workitem['System.AreaPath'] == "Test Agile"

def test_workitem_fields_custom(self, workitem):
assert workitem['Custom.Bug.Type'] == "Manual Test Case"

@pytest.mark.httpretty
def test_workitem_field_update(self, workitem):
workitem['Reason'] = "Canceled"
Expand All @@ -57,6 +115,13 @@ def test_workitem_fields_case_ins(self, workitem):
assert workitem['ReaSon'] == "New"
assert workitem['AREAPath'] == "Test Agile"

def test_workitem_parent_id(self, workitem):
assert workitem.parent_id == 110

def test_workitem_parent_with_child_only(self, workitem_with_child_only):
assert workitem_with_child_only.parent_id is None
assert workitem_with_child_only.child_ids == [10, 11]


class TestChangeset(object):
@pytest.fixture()
Expand Down
13 changes: 8 additions & 5 deletions tfs/connection.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import requests

from tfs.tfs import *
from tfs.resources import *


def batch(iterable, n=1):
Expand All @@ -18,7 +18,7 @@ class TFSAPI:
def __init__(self, server_url, project, user, password, verify=False):
if user is None or password is None:
raise ValueError('User name and api-key must be specified!')
self.rest_client = TFSClient(server_url, project=project, user=user, password=password, verify=verify)
self.rest_client = _HTTPClient(server_url, project=project, user=user, password=password, verify=verify)

def get_tfs_object(self, uri, payload=None, object_class=TFSObject):
""" Send requests and return any object in TFS """
Expand All @@ -41,8 +41,8 @@ def __get_workitems(self, work_items_ids, fields=None):
object_class=Workitem)
return workitems

def get_workitem(self, id, fields=None):
return self.get_workitems(id, fields)[0]
def get_workitem(self, id_, fields=None):
return self.get_workitems(id_, fields)[0]

def get_workitems(self, work_items_ids, fields=None, batch_size=50):
if isinstance(work_items_ids, int):
Expand Down Expand Up @@ -72,6 +72,9 @@ def get_changesets(self, from_=None, to_=None, item_path=None, top=10000):
def get_projects(self):
return self.get_tfs_object('projects', object_class=Projects)

def get_project(self, name):
return self.get_tfs_object('projects/{}'.format(name), object_class=Projects)

def update_workitem(self, work_item_id, update_data):
raw = self.rest_client.send_patch('wit/workitems/{id}?api-version=1.0'.format(id=work_item_id),
data=update_data,
Expand All @@ -83,7 +86,7 @@ class TFSClientError(Exception):
pass


class TFSClient:
class _HTTPClient:
def __init__(self, base_url, project, user, password, verify=False):
if not base_url.endswith('/'):
base_url += '/'
Expand Down
Loading

0 comments on commit 2d61885

Please sign in to comment.