Skip to content

Commit 66fbb1b

Browse files
committed
Initial refactor for v4 of sdk.
1 parent e76ef94 commit 66fbb1b

File tree

2,797 files changed

+117598
-146761
lines changed

Some content is hidden

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

2,797 files changed

+117598
-146761
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
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+
import pkg_resources
6+
pkg_resources.declare_namespace(__name__)
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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 base64
7+
import json
8+
import logging
9+
import os
10+
import time
11+
try:
12+
import collections.abc as collections
13+
except ImportError:
14+
import collections
15+
16+
17+
logger = logging.getLogger(__name__)
18+
19+
20+
class FileCache(collections.MutableMapping):
21+
"""A simple dict-like class that is backed by a JSON file.
22+
23+
All direct modifications will save the file. Indirect modifications should
24+
be followed by a call to `save_with_retry` or `save`.
25+
"""
26+
27+
def __init__(self, file_name, max_age=0):
28+
super(FileCache, self).__init__()
29+
self.file_name = file_name
30+
self.max_age = max_age
31+
self.data = {}
32+
self.initial_load_occurred = False
33+
34+
def load(self):
35+
self.data = {}
36+
try:
37+
if os.path.isfile(self.file_name):
38+
if self.max_age > 0 and os.stat(self.file_name).st_mtime + self.max_age < time.time():
39+
logger.debug('Cache file expired: %s', file=self.file_name)
40+
os.remove(self.file_name)
41+
else:
42+
logger.debug('Loading cache file: %s', self.file_name)
43+
self.data = get_file_json(self.file_name, throw_on_empty=False) or {}
44+
else:
45+
logger.debug('Cache file does not exist: %s', self.file_name)
46+
except Exception as ex:
47+
logger.debug(ex, exc_info=True)
48+
# file is missing or corrupt so attempt to delete it
49+
try:
50+
os.remove(self.file_name)
51+
except Exception as ex2:
52+
logger.debug(ex2, exc_info=True)
53+
self.initial_load_occurred = True
54+
55+
def save(self):
56+
self._check_for_initial_load()
57+
self._save()
58+
59+
def _save(self):
60+
if self.file_name:
61+
with os.fdopen(os.open(self.file_name, os.O_RDWR | os.O_CREAT | os.O_TRUNC, 0o600), 'w+') as cred_file:
62+
cred_file.write(json.dumps(self.data))
63+
64+
def save_with_retry(self, retries=5):
65+
self._check_for_initial_load()
66+
for _ in range(retries - 1):
67+
try:
68+
self.save()
69+
break
70+
except OSError:
71+
time.sleep(0.1)
72+
else:
73+
self.save()
74+
75+
def clear(self):
76+
if os.path.isfile(self.file_name):
77+
logger.info("Deleting file: " + self.file_name)
78+
os.remove(self.file_name)
79+
else:
80+
logger.info("File does not exist: " + self.file_name)
81+
82+
def get(self, key, default=None):
83+
self._check_for_initial_load()
84+
return self.data.get(key, default)
85+
86+
def __getitem__(self, key):
87+
self._check_for_initial_load()
88+
return self.data.setdefault(key, {})
89+
90+
def __setitem__(self, key, value):
91+
self._check_for_initial_load()
92+
self.data[key] = value
93+
self.save_with_retry()
94+
95+
def __delitem__(self, key):
96+
self._check_for_initial_load()
97+
del self.data[key]
98+
self.save_with_retry()
99+
100+
def __iter__(self):
101+
self._check_for_initial_load()
102+
return iter(self.data)
103+
104+
def __len__(self):
105+
self._check_for_initial_load()
106+
return len(self.data)
107+
108+
def _check_for_initial_load(self):
109+
if not self.initial_load_occurred:
110+
self.load()
111+
112+
113+
def get_cache_dir():
114+
azure_devops_cache_dir = os.getenv('AZURE_DEVOPS_CACHE_DIR', None)\
115+
or os.path.expanduser(os.path.join('~', '.azure-devops', 'python-sdk', 'cache'))
116+
if not os.path.exists(azure_devops_cache_dir):
117+
os.makedirs(azure_devops_cache_dir)
118+
return azure_devops_cache_dir
119+
120+
121+
DEFAULT_MAX_AGE = 3600 * 12 # 12 hours
122+
DEFAULT_CACHE_DIR = get_cache_dir()
123+
124+
125+
def get_cache(name, max_age=DEFAULT_MAX_AGE, cache_dir=DEFAULT_CACHE_DIR):
126+
file_name = os.path.join(cache_dir, name + '.json')
127+
return FileCache(file_name, max_age)
128+
129+
130+
OPTIONS_CACHE = get_cache('options')
131+
RESOURCE_CACHE = get_cache('resources')
132+
133+
134+
# Code below this point from azure-cli-core
135+
# https://github.com/Azure/azure-cli/blob/master/src/azure-cli-core/azure/cli/core/util.py
136+
137+
def get_file_json(file_path, throw_on_empty=True, preserve_order=False):
138+
content = read_file_content(file_path)
139+
if not content and not throw_on_empty:
140+
return None
141+
return shell_safe_json_parse(content, preserve_order)
142+
143+
144+
def read_file_content(file_path, allow_binary=False):
145+
from codecs import open as codecs_open
146+
# Note, always put 'utf-8-sig' first, so that BOM in WinOS won't cause trouble.
147+
for encoding in ['utf-8-sig', 'utf-8', 'utf-16', 'utf-16le', 'utf-16be']:
148+
try:
149+
with codecs_open(file_path, encoding=encoding) as f:
150+
logger.debug("attempting to read file %s as %s", file_path, encoding)
151+
return f.read()
152+
except UnicodeDecodeError:
153+
if allow_binary:
154+
with open(file_path, 'rb') as input_file:
155+
logger.debug("attempting to read file %s as binary", file_path)
156+
return base64.b64encode(input_file.read()).decode("utf-8")
157+
else:
158+
raise
159+
except UnicodeError:
160+
pass
161+
162+
raise ValueError('Failed to decode file {} - unknown decoding'.format(file_path))
163+
164+
165+
def shell_safe_json_parse(json_or_dict_string, preserve_order=False):
166+
""" Allows the passing of JSON or Python dictionary strings. This is needed because certain
167+
JSON strings in CMD shell are not received in main's argv. This allows the user to specify
168+
the alternative notation, which does not have this problem (but is technically not JSON). """
169+
try:
170+
if not preserve_order:
171+
return json.loads(json_or_dict_string)
172+
from collections import OrderedDict
173+
return json.loads(json_or_dict_string, object_pairs_hook=OrderedDict)
174+
except ValueError:
175+
import ast
176+
return ast.literal_eval(json_or_dict_string)
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# coding=utf-8
2+
# --------------------------------------------------------------------------
3+
# Generated file, DO NOT EDIT
4+
# Changes may cause incorrect behavior and will be lost if the code is
5+
# regenerated.
6+
# --------------------------------------------------------------------------
7+
8+
from msrest.serialization import Model
9+
10+
11+
class ApiResourceLocation(Model):
12+
"""ApiResourceLocation.
13+
"""
14+
15+
_attribute_map = {
16+
'id': {'key': 'id', 'type': 'str'},
17+
'area': {'key': 'area', 'type': 'str'},
18+
'resource_name': {'key': 'resourceName', 'type': 'str'},
19+
'route_template': {'key': 'routeTemplate', 'type': 'str'},
20+
'resource_version': {'key': 'resourceVersion', 'type': 'int'},
21+
'min_version': {'key': 'minVersion', 'type': 'float'},
22+
'max_version': {'key': 'maxVersion', 'type': 'float'},
23+
'released_version': {'key': 'releasedVersion', 'type': 'str'},
24+
}
25+
26+
def __init__(self, id=None, area=None, resource_name=None,
27+
route_template=None, resource_version=None,
28+
min_version=None, max_version=None,
29+
released_version=None):
30+
super(ApiResourceLocation, self).__init__()
31+
self.id = id
32+
self.area = area
33+
self.resource_name = resource_name
34+
self.route_template = route_template
35+
self.resource_version = resource_version
36+
self.min_version = min_version
37+
self.max_version = max_version
38+
self.released_version = released_version
39+
40+
41+
class CustomerIntelligenceEvent(Model):
42+
"""CustomerIntelligenceEvent.
43+
44+
:param area:
45+
:type area: str
46+
:param feature:
47+
:type feature: str
48+
:param properties:
49+
:type properties: dict
50+
"""
51+
52+
_attribute_map = {
53+
'area': {'key': 'area', 'type': 'str'},
54+
'feature': {'key': 'feature', 'type': 'str'},
55+
'properties': {'key': 'properties', 'type': '{object}'}
56+
}
57+
58+
def __init__(self, area=None, feature=None, properties=None):
59+
super(CustomerIntelligenceEvent, self).__init__()
60+
self.area = area
61+
self.feature = feature
62+
self.properties = properties
63+
64+
65+
class ImproperException(Model):
66+
"""ImproperException.
67+
:param message:
68+
:type message: str
69+
"""
70+
71+
_attribute_map = {
72+
'message': {'key': 'Message', 'type': 'str'}
73+
}
74+
75+
def __init__(self, message=None):
76+
super(ImproperException, self).__init__()
77+
self.message = message
78+
79+
80+
class ResourceAreaInfo(Model):
81+
"""ResourceAreaInfo.
82+
83+
:param id:
84+
:type id: str
85+
:param location_url:
86+
:type location_url: str
87+
:param name:
88+
:type name: str
89+
"""
90+
91+
_attribute_map = {
92+
'id': {'key': 'id', 'type': 'str'},
93+
'location_url': {'key': 'locationUrl', 'type': 'str'},
94+
'name': {'key': 'name', 'type': 'str'}
95+
}
96+
97+
def __init__(self, id=None, location_url=None, name=None):
98+
super(ResourceAreaInfo, self).__init__()
99+
self.id = id
100+
self.location_url = location_url
101+
self.name = name
102+
103+
104+
class SystemException(Model):
105+
"""SystemException.
106+
:param class_name:
107+
:type class_name: str
108+
:param inner_exception:
109+
:type inner_exception: :class:`SystemException <vsts.models.SystemException>`
110+
:param message:
111+
:type message: str
112+
"""
113+
114+
_attribute_map = {
115+
'class_name': {'key': 'ClassName', 'type': 'str'},
116+
'message': {'key': 'Message', 'type': 'str'},
117+
'inner_exception': {'key': 'InnerException', 'type': 'SystemException'}
118+
}
119+
120+
def __init__(self, class_name=None, message=None, inner_exception=None):
121+
super(SystemException, self).__init__()
122+
self.class_name = class_name
123+
self.message = message
124+
self.inner_exception = inner_exception
125+
126+
127+
class VssJsonCollectionWrapperBase(Model):
128+
"""VssJsonCollectionWrapperBase.
129+
130+
:param count:
131+
:type count: int
132+
"""
133+
134+
_attribute_map = {
135+
'count': {'key': 'count', 'type': 'int'}
136+
}
137+
138+
def __init__(self, count=None):
139+
super(VssJsonCollectionWrapperBase, self).__init__()
140+
self.count = count
141+
142+
143+
class VssJsonCollectionWrapper(VssJsonCollectionWrapperBase):
144+
"""VssJsonCollectionWrapper.
145+
146+
:param count:
147+
:type count: int
148+
:param value:
149+
:type value: object
150+
"""
151+
152+
_attribute_map = {
153+
'count': {'key': 'count', 'type': 'int'},
154+
'value': {'key': 'value', 'type': 'object'}
155+
}
156+
157+
def __init__(self, count=None, value=None):
158+
super(VssJsonCollectionWrapper, self).__init__(count=count)
159+
self.value = value
160+
161+
162+
class WrappedException(Model):
163+
"""WrappedException.
164+
:param exception_id:
165+
:type exception_id: str
166+
:param inner_exception:
167+
:type inner_exception: :class:`WrappedException <vsts.models.WrappedException>`
168+
:param message:
169+
:type message: str
170+
:param type_name:
171+
:type type_name: str
172+
:param type_key:
173+
:type type_key: str
174+
:param error_code:
175+
:type error_code: int
176+
:param event_id:
177+
:type event_id: int
178+
:param custom_properties:
179+
:type custom_properties: dict
180+
"""
181+
182+
_attribute_map = {
183+
'exception_id': {'key': '$id', 'type': 'str'},
184+
'inner_exception': {'key': 'innerException', 'type': 'WrappedException'},
185+
'message': {'key': 'message', 'type': 'str'},
186+
'type_name': {'key': 'typeName', 'type': 'str'},
187+
'type_key': {'key': 'typeKey', 'type': 'str'},
188+
'error_code': {'key': 'errorCode', 'type': 'int'},
189+
'event_id': {'key': 'eventId', 'type': 'int'},
190+
'custom_properties': {'key': 'customProperties', 'type': '{object}'}
191+
}
192+
193+
def __init__(self, exception_id=None, inner_exception=None, message=None,
194+
type_name=None, type_key=None, error_code=None, event_id=None, custom_properties=None):
195+
super(WrappedException, self).__init__()
196+
self.exception_id = exception_id
197+
self.inner_exception = inner_exception
198+
self.message = message
199+
self.type_name = type_name
200+
self.type_key = type_key
201+
self.error_code = error_code
202+
self.event_id = event_id
203+
self.custom_properties = custom_properties
204+
205+
206+
__all__ = [
207+
'ApiResourceLocation',
208+
'CustomerIntelligenceEvent',
209+
'ImproperException',
210+
'ResourceAreaInfo',
211+
'SystemException',
212+
'VssJsonCollectionWrapperBase',
213+
'VssJsonCollectionWrapper',
214+
'WrappedException'
215+
]

0 commit comments

Comments
 (0)