Skip to content

Commit

Permalink
Merge pull request #37 from SurveyMonkey/future
Browse files Browse the repository at this point in the history
A brand new API - `pyteamcity.future`
  • Loading branch information
msabramo authored Aug 10, 2016
2 parents 4807c87 + d50f9ec commit c06ddef
Show file tree
Hide file tree
Showing 38 changed files with 1,929 additions and 3 deletions.
20 changes: 20 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,27 @@ env:
- TOXENV=py33
- TOXENV=py34
- TOXENV=pypy
- TOXENV=flake8
install:
- travis_retry pip install tox
script:
- tox

after_script:
- |
env | sort
if [ ! -f coverage.xml ]; then
echo "No coverage.xml found; skipping sending coverage to couverture.io"
return
fi
if [ $TRAVIS_PULL_REQUEST != false ]; then
REAL_COMMIT=$(git log -n 1 $TRAVIS_COMMIT_RANGE --format="%H")
PR_NUM=$TRAVIS_PULL_REQUEST
else
REAL_COMMIT=$TRAVIS_COMMIT
fi
curl \
--verbose --insecure \
--header "X-Couverture-Pull-Request: $PR_NUM" \
--data-binary @coverage.xml \
"https://app.couverture.io/coverage/github/SurveyMonkey/pyteamcity/$REAL_COMMIT/$TOXENV"
1 change: 1 addition & 0 deletions pyteamcity/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .legacy import *
7 changes: 7 additions & 0 deletions pyteamcity/future/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
@todo: Allow canceling builds
@todo: docstrings for classes
"""

from .page_joiner import PageJoiner # noqa
from .teamcity import TeamCity # noqa
133 changes: 133 additions & 0 deletions pyteamcity/future/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import requests

from .core.parameter import Parameter
from .core.queryset import QuerySet

from .agent_pool import AgentPool, AgentPoolQuerySet


class Agent(object):
"""
(Pdb++) agent._data_dict.keys()
[u'typeId', u'name', u'ip', u'enabled', u'properties',
u'uptodate', u'href', u'connected', u'authorized',
u'id', u'pool']
"""

def __init__(self, id, href, name, type_id, ip,
enabled, connected, authorized,
pool_id,
query_set, data_dict=None):
self.id = id
self.href = href
self.name = name
self.type_id = type_id
self.ip = ip
self.enabled = enabled
self.connected = connected
self.authorized = authorized
self.pool_id = pool_id
self.query_set = query_set
self._data_dict = data_dict

def __repr__(self):
return '<%s.%s: id=%r name=%r>' % (
self.__module__,
self.__class__.__name__,
self.id,
self.name)

@classmethod
def from_dict(cls, d, query_set=None):
return cls(
id=d.get('id'),
href=d.get('href'),
name=d.get('name'),
type_id=d.get('typeId'),
ip=d.get('ip'),
enabled=d.get('enabled'),
connected=d.get('connected'),
authorized=d.get('authorized'),
pool_id=d.get('pool', {}).get('id'),
query_set=query_set,
data_dict=d)

@property
def pool(self):
teamcity = self.query_set.teamcity
if 'agentPool' in self._data_dict:
agent_pool = AgentPool.from_dict(self._data_dict.get('pool'))
else:
agent_pool = AgentPoolQuerySet(teamcity).get(id=self.pool_id)
return agent_pool

@property
def parameters_dict(self):
d = {}

for param in self._data_dict['properties']['property']:
param_obj = Parameter()
if 'value' in param:
param_obj.value = param['value']
if 'type' in param:
param_obj.ptype = param['type']
d[param['name']] = param_obj

return d

@property
def teamcity(self):
return self.query_set.teamcity

def set_enabled(self, enabled_str, dry_run=False):
extra_headers = {'Content-Type': 'text/plain',
'Accept': 'text/plain'}
req = self._put_request('enabled', data=enabled_str,
extra_headers=extra_headers)
if dry_run:
return req
return self.teamcity.session.send(req)

def _put_request(self, relative_uri, data, extra_headers):
url = self._get_url() + '/' + relative_uri
headers = dict(self.teamcity.session.headers)
headers.update(extra_headers)
req = requests.Request(
method='PUT',
url=url,
data=data,
headers=headers)
prepped = self.teamcity.session.prepare_request(req)
return prepped

def _get_url(self):
return AgentQuerySet(self.teamcity).get(id=self.id, just_url=True)

def enable(self, dry_run=False):
return self.set_enabled('true', dry_run=dry_run)

def disable(self, dry_run=False):
return self.set_enabled('false', dry_run=dry_run)


class AgentQuerySet(QuerySet):
uri = '/app/rest/agents/'
_entity_factory = Agent

def filter(self, id=None, name=None,
connected=None, authorized=None, enabled=None):
if id is not None:
self._add_pred('id', id)
if name is not None:
self._add_pred('name', name)
if connected is not None:
self._add_pred('connected', connected)
if authorized is not None:
self._add_pred('authorized', authorized)
if enabled is not None:
self._add_pred('enabled', enabled)
return self

def __iter__(self):
return (self._entity_factory.from_dict(d, self)
for d in self._data()['agent'])
60 changes: 60 additions & 0 deletions pyteamcity/future/agent_pool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from .core.queryset import QuerySet
from .project import Project


class AgentPool(object):
def __init__(self, id, href, name,
query_set, data_dict=None):
self.id = id
self.href = href
self.name = name
self.query_set = query_set
self._data_dict = data_dict

def __repr__(self):
return '<%s.%s: id=%r name=%r>' % (
self.__module__,
self.__class__.__name__,
self.id,
self.name)

@classmethod
def from_dict(cls, d, query_set=None):
return cls(
id=d.get('id'),
href=d.get('href'),
name=d.get('name'),
query_set=query_set,
data_dict=d)

@property
def agents(self):
from .agent import Agent

ret = []
for agent in self._data_dict['agents']['agent']:
ret.append(Agent.from_dict(agent))
return ret

@property
def projects(self):
ret = []
for project in self._data_dict['projects']['project']:
ret.append(Project.from_dict(project))
return ret


class AgentPoolQuerySet(QuerySet):
uri = '/app/rest/agentPools/'
_entity_factory = AgentPool

def filter(self, id=None, name=None):
if id is not None:
self._add_pred('id', id)
if name is not None:
self._add_pred('name', name)
return self

def __iter__(self):
return (self._entity_factory.from_dict(d, self)
for d in self._data()['agentPool'])
118 changes: 118 additions & 0 deletions pyteamcity/future/artifact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import fnmatch
import os

from . import exceptions
from .core.utils import parse_date_string


class Artifact(object):
def __init__(self, build, path=''):
self.build = build
self.path = path
teamcity = self.build.build_query_set.teamcity
url = self.build.api_url + '/artifacts/metadata/' + self.path
res = teamcity.session.get(url)
if not res.ok:
if res.status_code == 404:
raise exceptions.ArtifactNotFound(path=path)
else:
raise exceptions.HTTPError(
status_code=res.status_code,
reason=res.reason,
text=res.text)
self._data = res.json()
self._metadata_url = url

@property
def name(self):
return self._data['name']

def getsize(self):
return self._data.get('size')

@property
def size(self):
return self.getsize()

@property
def modification_time(self):
return parse_date_string(self._data['modificationTime'])

def splitext(self):
return os.path.splitext(self.name)

@property
def ext(self):
return self.splitext()[1]

def fnmatch(self, pattern):
return fnmatch.fnmatch(self.name, pattern)

@property
def content_href(self):
return self._data.get('content', {}).get('href')

def isdir(self):
return self.content_href is None

def isfile(self):
return self.content_href is not None

def __repr__(self):
return '<%s.%s: build.id=%r name=%r size=%r>' % (
self.__module__,
self.__class__.__name__,
self.build.id,
self.name,
self.size)

def content(self):
if not self.isfile():
raise exceptions.IllegalOperation(
'Calling the `content` method on a non-file artifact'
' (%r) is not allowed' % self)
teamcity = self.build.build_query_set.teamcity
url = teamcity.base_base_url + self.content_href
res = teamcity.session.get(url)
if not res.ok:
raise exceptions.HTTPError(
status_code=res.status_code,
reason=res.reason,
text=res.text)
return res.content

def get_artifact_by_path(self, path):
return Artifact(build=self.build, path=self.path + '/' + path)

def __div__(self, path):
return self.get_artifact_by_path(path)

def listdir(self, pattern=None):
teamcity = self.build.build_query_set.teamcity
url = self.build.api_url + '/artifacts/children/' + self.path
res = teamcity.session.get(url)
if not res.ok:
raise exceptions.HTTPError(
status_code=res.status_code,
reason=res.reason,
text=res.text)
data = res.json()
if data.get('count', 0) == 0 or 'file' not in data:
return []
ret = []
for f in data['file']:
if pattern is None or fnmatch.fnmatch(f['name'], pattern):
path = self.path + '/' + f['name']
path = path.lstrip('/')
ret.append(Artifact(build=self.build, path=path))
return ret

def dirname(self):
path = os.path.dirname(self.path)
return Artifact(build=self.build, path=path)

def files(self, pattern=None):
return [x for x in self.listdir(pattern) if x.isfile()]

def dirs(self, pattern=None):
return [x for x in self.listdir(pattern) if x.isdir()]
Loading

0 comments on commit c06ddef

Please sign in to comment.