Skip to content

Commit a713b9b

Browse files
authored
6.0 API
6.0 API
2 parents ac0fce0 + 7cee894 commit a713b9b

File tree

189 files changed

+26164
-12040
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

189 files changed

+26164
-12040
lines changed

.github/workflows/pythonpackage.yml

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Python package
2+
3+
on: [push]
4+
5+
jobs:
6+
build:
7+
8+
runs-on: ubuntu-latest
9+
strategy:
10+
max-parallel: 5
11+
matrix:
12+
python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
13+
14+
steps:
15+
- uses: actions/checkout@v1
16+
- name: Set up Python ${{ matrix.python-version }}
17+
uses: actions/setup-python@v1
18+
with:
19+
python-version: ${{ matrix.python-version }}
20+
- name: Install dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
pip install msrest
24+
- name: Python compile
25+
run: |
26+
python -m compileall .
27+
- name: Lint with flake8
28+
run: |
29+
pip install flake8
30+
# stop the build if there are Python syntax errors or undefined names
31+
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
32+
- name: Test with pytest
33+
run: |
34+
pip install pytest
35+
pytest

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
env/**
1818
dist/
1919
lib/
20+
.eggs/
2021

2122
# Build results
2223
[Dd]ebug/
@@ -298,8 +299,8 @@ vsts/build/bdist.win32/
298299

299300
# don't ignore release management client
300301
!azure-devops/azure/devops/released/release
301-
!azure-devops/azure/devops/v5_0/release
302302
!azure-devops/azure/devops/v5_1/release
303+
!azure-devops/azure/devops/v6_0/release
303304

304305
# ignore private folder for testing reported issues
305306
issues/

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
[![Python package](https://github.com/microsoft/azure-devops-python-api/workflows/Python%20package/badge.svg)](https://github.com/microsoft/azure-devops-python-api/actions)
12
[![Build Status](https://dev.azure.com/mseng/vsts-cli/_apis/build/status/vsts-python-api?branchName=dev)](https://dev.azure.com/mseng/vsts-cli/_build/latest?definitionId=5904&branchName=dev)
23
[![Python](https://img.shields.io/pypi/pyversions/azure-devops.svg)](https://pypi.python.org/pypi/azure-devops)
34

azure-devops/azure/devops/client.py

+50-27
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ def __init__(self, base_url=None, creds=None):
3636
_base_client_models = {k: v for k, v in _models.__dict__.items() if isinstance(v, type)}
3737
self._base_deserialize = Deserializer(_base_client_models)
3838
self._base_serialize = Serializer(_base_client_models)
39-
self._all_host_types_locations = None
40-
self._locations = None
39+
self._all_host_types_locations = {}
40+
self._locations = {}
4141
self._suppress_fedauth_redirect = True
4242
self._force_msa_pass_through = True
4343
self.normalized_url = Client._normalize_url(base_url)
@@ -76,7 +76,7 @@ def _send(self, http_method, location_id, version, route_values=None,
7676
route_values=route_values,
7777
query_parameters=query_parameters)
7878
negotiated_version = self._negotiate_request_version(
79-
self._get_resource_location(location_id),
79+
self._get_resource_location(self.normalized_url, location_id),
8080
version)
8181

8282
if version != negotiated_version:
@@ -116,19 +116,31 @@ def _unwrap_collection(self, response):
116116

117117
def _create_request_message(self, http_method, location_id, route_values=None,
118118
query_parameters=None):
119-
location = self._get_resource_location(location_id)
119+
location = self._get_organization_resource_location(location_id)
120+
deployment_level = False
121+
deployment_url = None
120122
if location is None:
121-
raise ValueError('API resource location ' + location_id + ' is not registered on '
122-
+ self.config.base_url + '.')
123+
logger.debug('API resource location ' + location_id + ' is not registered on ' + self.config.base_url + '.')
124+
deployment_url = self._get_deployment_url()
125+
if deployment_url is not None:
126+
logger.debug('Checking for location at deployment level: ' + deployment_url)
127+
location = self._get_resource_location(deployment_url, location_id)
128+
deployment_level = True
129+
if location is None:
130+
raise ValueError('API resource location ' + location_id + ' is not registered on '
131+
+ self.config.base_url + '.')
123132
if route_values is None:
124133
route_values = {}
125134
route_values['area'] = location.area
126135
route_values['resource'] = location.resource_name
127136
route_template = self._remove_optional_route_parameters(location.route_template,
128137
route_values)
129138
logger.debug('Route template: %s', location.route_template)
130-
url = self._client.format_url(route_template, **route_values)
131-
request = ClientRequest(method=http_method, url=self._client.format_url(url))
139+
if not deployment_level:
140+
url = self._client.format_url(route_template, **route_values)
141+
else:
142+
url = self._client.format_url(deployment_url + route_template, **route_values)
143+
request = ClientRequest(method=http_method, url=url)
132144
if query_parameters:
133145
request.format_parameters(query_parameters)
134146
return request
@@ -144,35 +156,46 @@ def _remove_optional_route_parameters(route_template, route_values):
144156
new_template = new_template + '/' + path_segment
145157
return new_template
146158

147-
def _get_resource_location(self, location_id):
148-
if self.config.base_url not in Client._locations_cache:
149-
Client._locations_cache[self.config.base_url] = self._get_resource_locations(all_host_types=False)
150-
for location in Client._locations_cache[self.config.base_url]:
159+
def _get_organization_resource_location(self, location_id):
160+
return self._get_resource_location(self.normalized_url, location_id)
161+
162+
def _get_deployment_url(self):
163+
pos = self.normalized_url.rfind('/')
164+
if pos > 0:
165+
deployment_url = self.normalized_url[:pos]
166+
if deployment_url.find('://') > 0:
167+
return deployment_url
168+
return None
169+
170+
def _get_resource_location(self, url, location_id):
171+
if url not in Client._locations_cache:
172+
Client._locations_cache[url] = self._get_resource_locations(url, all_host_types=False)
173+
for location in Client._locations_cache[url]:
151174
if location.id == location_id:
152175
return location
153176

154-
def _get_resource_locations(self, all_host_types):
177+
def _get_resource_locations(self, url, all_host_types):
155178
# Check local client's cached Options first
156179
if all_host_types:
157-
if self._all_host_types_locations is not None:
158-
return self._all_host_types_locations
159-
elif self._locations is not None:
160-
return self._locations
180+
if url in self._all_host_types_locations:
181+
return self._all_host_types_locations[url]
182+
elif url in self._locations:
183+
return self._locations[url]
161184

162185
# Next check for options cached on disk
163-
if not all_host_types and OPTIONS_FILE_CACHE[self.normalized_url]:
186+
if not all_host_types and OPTIONS_FILE_CACHE[url]:
164187
try:
165-
logger.debug('File cache hit for options on: %s', self.normalized_url)
166-
self._locations = self._base_deserialize.deserialize_data(OPTIONS_FILE_CACHE[self.normalized_url],
167-
'[ApiResourceLocation]')
168-
return self._locations
188+
logger.debug('File cache hit for options on: %s', url)
189+
self._locations[url] = self._base_deserialize.deserialize_data(OPTIONS_FILE_CACHE[url],
190+
'[ApiResourceLocation]')
191+
return self._locations[url]
169192
except DeserializationError as ex:
170193
logger.debug(ex, exc_info=True)
171194
else:
172-
logger.debug('File cache miss for options on: %s', self.normalized_url)
195+
logger.debug('File cache miss for options on: %s', url)
173196

174197
# Last resort, make the call to the server
175-
options_uri = self._combine_url(self.config.base_url, '_apis')
198+
options_uri = self._combine_url(url, '_apis')
176199
request = ClientRequest(method='OPTIONS', url=self._client.format_url(options_uri))
177200
if all_host_types:
178201
query_parameters = {'allHostTypes': True}
@@ -190,11 +213,11 @@ def _get_resource_locations(self, all_host_types):
190213
returned_locations = self._base_deserialize('[ApiResourceLocation]',
191214
collection)
192215
if all_host_types:
193-
self._all_host_types_locations = returned_locations
216+
self._all_host_types_locations[url] = returned_locations
194217
else:
195-
self._locations = returned_locations
218+
self._locations[url] = returned_locations
196219
try:
197-
OPTIONS_FILE_CACHE[self.normalized_url] = wrapper.value
220+
OPTIONS_FILE_CACHE[url] = wrapper.value
198221
except SerializationError as ex:
199222
logger.debug(ex, exc_info=True)
200223
return returned_locations

azure-devops/azure/devops/connection.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
from .client_configuration import ClientConfiguration
1111
from .exceptions import AzureDevOpsClientRequestError
1212
from .released.client_factory import ClientFactory
13-
from .v5_0.location.location_client import LocationClient
14-
from .v5_0.client_factory import ClientFactoryV5_0
13+
from .v5_1.location.location_client import LocationClient
1514
from .v5_1.client_factory import ClientFactoryV5_1
15+
from .v6_0.client_factory import ClientFactoryV6_0
1616

1717
logger = logging.getLogger(__name__)
1818

@@ -33,8 +33,8 @@ def __init__(self, base_url=None, creds=None, user_agent=None):
3333
self._creds = creds
3434
self._resource_areas = None
3535
self.clients = ClientFactory(self)
36-
self.clients_v5_0 = ClientFactoryV5_0(self)
3736
self.clients_v5_1 = ClientFactoryV5_1(self)
37+
self.clients_v6_0 = ClientFactoryV6_0(self)
3838
self.use_fiddler = False
3939

4040
def get_client(self, client_type):
@@ -109,8 +109,9 @@ def _get_resource_areas(self, force=False):
109109
if not force and RESOURCE_FILE_CACHE[location_client.normalized_url]:
110110
try:
111111
logger.debug('File cache hit for resources on: %s', location_client.normalized_url)
112-
self._resource_areas = location_client._base_deserialize.deserialize_data(RESOURCE_FILE_CACHE[location_client.normalized_url],
113-
'[ResourceAreaInfo]')
112+
self._resource_areas = location_client._base_deserialize.deserialize_data(
113+
RESOURCE_FILE_CACHE[location_client.normalized_url],
114+
'[ResourceAreaInfo]')
114115
return self._resource_areas
115116
except Exception as ex:
116117
logger.debug(ex, exc_info=True)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
import pprint
7+
import unittest
8+
9+
from msrest import Deserializer
10+
from msrest.universal_http import HTTPClientResponse
11+
12+
13+
class _TestResponse(HTTPClientResponse):
14+
def __init__(self, text):
15+
super(_TestResponse, self).__init__(request=None, internal_response=None)
16+
self._text = text
17+
18+
def text(self, encoding=None):
19+
return self._text
20+
21+
22+
class TestDeserialization(unittest.TestCase):
23+
24+
# https://github.com/microsoft/azure-devops-python-api/issues/268
25+
def test_deserialization_issue_268_51(self):
26+
from azure.devops.v5_1.task_agent import models
27+
self._test_deserialization(models.__dict__.items(), _268_type, _268_json)
28+
29+
# https://github.com/microsoft/azure-devops-python-api/issues/268
30+
def test_deserialization_issue_268_60(self):
31+
from azure.devops.v6_0.task_agent import models
32+
self._test_deserialization(models.__dict__.items(), _268_type, _268_json)
33+
34+
@staticmethod
35+
def _test_deserialization(models, data_type, json):
36+
client_models = {k: v for k, v in models if isinstance(v, type)}
37+
deserializer = Deserializer(client_models)
38+
response = _TestResponse(json)
39+
task_agent_response = deserializer(data_type, response)
40+
pprint.pprint(task_agent_response.__dict__)
41+
42+
43+
if __name__ == '__main__':
44+
unittest.main()
45+
46+
_268_type = 'TaskAgentReference'
47+
_268_json = '{"id":0,"name":null,"version":null,"osDescription":"Foo","provisioningState":null}'

azure-devops/azure/devops/v5_0/boards/__init__.py

-40
This file was deleted.

azure-devops/azure/devops/v5_0/boards/boards_client.py

-27
This file was deleted.

0 commit comments

Comments
 (0)