Skip to content

Commit

Permalink
6.0 API
Browse files Browse the repository at this point in the history
6.0 API
  • Loading branch information
tedchamb authored Dec 9, 2019
2 parents ac0fce0 + 7cee894 commit a713b9b
Show file tree
Hide file tree
Showing 189 changed files with 26,164 additions and 12,040 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Python package

on: [push]

jobs:
build:

runs-on: ubuntu-latest
strategy:
max-parallel: 5
matrix:
python-version: [2.7, 3.5, 3.6, 3.7, 3.8]

steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install msrest
- name: Python compile
run: |
python -m compileall .
- name: Lint with flake8
run: |
pip install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
- name: Test with pytest
run: |
pip install pytest
pytest
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
env/**
dist/
lib/
.eggs/

# Build results
[Dd]ebug/
Expand Down Expand Up @@ -298,8 +299,8 @@ vsts/build/bdist.win32/

# don't ignore release management client
!azure-devops/azure/devops/released/release
!azure-devops/azure/devops/v5_0/release
!azure-devops/azure/devops/v5_1/release
!azure-devops/azure/devops/v6_0/release

# ignore private folder for testing reported issues
issues/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[![Python package](https://github.com/microsoft/azure-devops-python-api/workflows/Python%20package/badge.svg)](https://github.com/microsoft/azure-devops-python-api/actions)
[![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)
[![Python](https://img.shields.io/pypi/pyversions/azure-devops.svg)](https://pypi.python.org/pypi/azure-devops)

Expand Down
77 changes: 50 additions & 27 deletions azure-devops/azure/devops/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ def __init__(self, base_url=None, creds=None):
_base_client_models = {k: v for k, v in _models.__dict__.items() if isinstance(v, type)}
self._base_deserialize = Deserializer(_base_client_models)
self._base_serialize = Serializer(_base_client_models)
self._all_host_types_locations = None
self._locations = None
self._all_host_types_locations = {}
self._locations = {}
self._suppress_fedauth_redirect = True
self._force_msa_pass_through = True
self.normalized_url = Client._normalize_url(base_url)
Expand Down Expand Up @@ -76,7 +76,7 @@ def _send(self, http_method, location_id, version, route_values=None,
route_values=route_values,
query_parameters=query_parameters)
negotiated_version = self._negotiate_request_version(
self._get_resource_location(location_id),
self._get_resource_location(self.normalized_url, location_id),
version)

if version != negotiated_version:
Expand Down Expand Up @@ -116,19 +116,31 @@ def _unwrap_collection(self, response):

def _create_request_message(self, http_method, location_id, route_values=None,
query_parameters=None):
location = self._get_resource_location(location_id)
location = self._get_organization_resource_location(location_id)
deployment_level = False
deployment_url = None
if location is None:
raise ValueError('API resource location ' + location_id + ' is not registered on '
+ self.config.base_url + '.')
logger.debug('API resource location ' + location_id + ' is not registered on ' + self.config.base_url + '.')
deployment_url = self._get_deployment_url()
if deployment_url is not None:
logger.debug('Checking for location at deployment level: ' + deployment_url)
location = self._get_resource_location(deployment_url, location_id)
deployment_level = True
if location is None:
raise ValueError('API resource location ' + location_id + ' is not registered on '
+ self.config.base_url + '.')
if route_values is None:
route_values = {}
route_values['area'] = location.area
route_values['resource'] = location.resource_name
route_template = self._remove_optional_route_parameters(location.route_template,
route_values)
logger.debug('Route template: %s', location.route_template)
url = self._client.format_url(route_template, **route_values)
request = ClientRequest(method=http_method, url=self._client.format_url(url))
if not deployment_level:
url = self._client.format_url(route_template, **route_values)
else:
url = self._client.format_url(deployment_url + route_template, **route_values)
request = ClientRequest(method=http_method, url=url)
if query_parameters:
request.format_parameters(query_parameters)
return request
Expand All @@ -144,35 +156,46 @@ def _remove_optional_route_parameters(route_template, route_values):
new_template = new_template + '/' + path_segment
return new_template

def _get_resource_location(self, location_id):
if self.config.base_url not in Client._locations_cache:
Client._locations_cache[self.config.base_url] = self._get_resource_locations(all_host_types=False)
for location in Client._locations_cache[self.config.base_url]:
def _get_organization_resource_location(self, location_id):
return self._get_resource_location(self.normalized_url, location_id)

def _get_deployment_url(self):
pos = self.normalized_url.rfind('/')
if pos > 0:
deployment_url = self.normalized_url[:pos]
if deployment_url.find('://') > 0:
return deployment_url
return None

def _get_resource_location(self, url, location_id):
if url not in Client._locations_cache:
Client._locations_cache[url] = self._get_resource_locations(url, all_host_types=False)
for location in Client._locations_cache[url]:
if location.id == location_id:
return location

def _get_resource_locations(self, all_host_types):
def _get_resource_locations(self, url, all_host_types):
# Check local client's cached Options first
if all_host_types:
if self._all_host_types_locations is not None:
return self._all_host_types_locations
elif self._locations is not None:
return self._locations
if url in self._all_host_types_locations:
return self._all_host_types_locations[url]
elif url in self._locations:
return self._locations[url]

# Next check for options cached on disk
if not all_host_types and OPTIONS_FILE_CACHE[self.normalized_url]:
if not all_host_types and OPTIONS_FILE_CACHE[url]:
try:
logger.debug('File cache hit for options on: %s', self.normalized_url)
self._locations = self._base_deserialize.deserialize_data(OPTIONS_FILE_CACHE[self.normalized_url],
'[ApiResourceLocation]')
return self._locations
logger.debug('File cache hit for options on: %s', url)
self._locations[url] = self._base_deserialize.deserialize_data(OPTIONS_FILE_CACHE[url],
'[ApiResourceLocation]')
return self._locations[url]
except DeserializationError as ex:
logger.debug(ex, exc_info=True)
else:
logger.debug('File cache miss for options on: %s', self.normalized_url)
logger.debug('File cache miss for options on: %s', url)

# Last resort, make the call to the server
options_uri = self._combine_url(self.config.base_url, '_apis')
options_uri = self._combine_url(url, '_apis')
request = ClientRequest(method='OPTIONS', url=self._client.format_url(options_uri))
if all_host_types:
query_parameters = {'allHostTypes': True}
Expand All @@ -190,11 +213,11 @@ def _get_resource_locations(self, all_host_types):
returned_locations = self._base_deserialize('[ApiResourceLocation]',
collection)
if all_host_types:
self._all_host_types_locations = returned_locations
self._all_host_types_locations[url] = returned_locations
else:
self._locations = returned_locations
self._locations[url] = returned_locations
try:
OPTIONS_FILE_CACHE[self.normalized_url] = wrapper.value
OPTIONS_FILE_CACHE[url] = wrapper.value
except SerializationError as ex:
logger.debug(ex, exc_info=True)
return returned_locations
Expand Down
11 changes: 6 additions & 5 deletions azure-devops/azure/devops/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
from .client_configuration import ClientConfiguration
from .exceptions import AzureDevOpsClientRequestError
from .released.client_factory import ClientFactory
from .v5_0.location.location_client import LocationClient
from .v5_0.client_factory import ClientFactoryV5_0
from .v5_1.location.location_client import LocationClient
from .v5_1.client_factory import ClientFactoryV5_1
from .v6_0.client_factory import ClientFactoryV6_0

logger = logging.getLogger(__name__)

Expand All @@ -33,8 +33,8 @@ def __init__(self, base_url=None, creds=None, user_agent=None):
self._creds = creds
self._resource_areas = None
self.clients = ClientFactory(self)
self.clients_v5_0 = ClientFactoryV5_0(self)
self.clients_v5_1 = ClientFactoryV5_1(self)
self.clients_v6_0 = ClientFactoryV6_0(self)
self.use_fiddler = False

def get_client(self, client_type):
Expand Down Expand Up @@ -109,8 +109,9 @@ def _get_resource_areas(self, force=False):
if not force and RESOURCE_FILE_CACHE[location_client.normalized_url]:
try:
logger.debug('File cache hit for resources on: %s', location_client.normalized_url)
self._resource_areas = location_client._base_deserialize.deserialize_data(RESOURCE_FILE_CACHE[location_client.normalized_url],
'[ResourceAreaInfo]')
self._resource_areas = location_client._base_deserialize.deserialize_data(
RESOURCE_FILE_CACHE[location_client.normalized_url],
'[ResourceAreaInfo]')
return self._resource_areas
except Exception as ex:
logger.debug(ex, exc_info=True)
Expand Down
File renamed without changes.
47 changes: 47 additions & 0 deletions azure-devops/azure/devops/issue_tests/test_issue_268.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import pprint
import unittest

from msrest import Deserializer
from msrest.universal_http import HTTPClientResponse


class _TestResponse(HTTPClientResponse):
def __init__(self, text):
super(_TestResponse, self).__init__(request=None, internal_response=None)
self._text = text

def text(self, encoding=None):
return self._text


class TestDeserialization(unittest.TestCase):

# https://github.com/microsoft/azure-devops-python-api/issues/268
def test_deserialization_issue_268_51(self):
from azure.devops.v5_1.task_agent import models
self._test_deserialization(models.__dict__.items(), _268_type, _268_json)

# https://github.com/microsoft/azure-devops-python-api/issues/268
def test_deserialization_issue_268_60(self):
from azure.devops.v6_0.task_agent import models
self._test_deserialization(models.__dict__.items(), _268_type, _268_json)

@staticmethod
def _test_deserialization(models, data_type, json):
client_models = {k: v for k, v in models if isinstance(v, type)}
deserializer = Deserializer(client_models)
response = _TestResponse(json)
task_agent_response = deserializer(data_type, response)
pprint.pprint(task_agent_response.__dict__)


if __name__ == '__main__':
unittest.main()

_268_type = 'TaskAgentReference'
_268_json = '{"id":0,"name":null,"version":null,"osDescription":"Foo","provisioningState":null}'
40 changes: 0 additions & 40 deletions azure-devops/azure/devops/v5_0/boards/__init__.py

This file was deleted.

27 changes: 0 additions & 27 deletions azure-devops/azure/devops/v5_0/boards/boards_client.py

This file was deleted.

Loading

0 comments on commit a713b9b

Please sign in to comment.