Skip to content

Commit

Permalink
Merge branch 'feature/readonly-cleanup' into 'develop'
Browse files Browse the repository at this point in the history
Feature/readonly cleanup

See merge request core/sevenbridges-python!78
  • Loading branch information
Dejan Knezevic committed Mar 9, 2021
2 parents 59607fc + 348fecc commit f9952f6
Show file tree
Hide file tree
Showing 33 changed files with 159 additions and 116 deletions.
10 changes: 5 additions & 5 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
-r requirements.txt

flake8==3.8.4
pytest==6.2.1
pytest-cov==2.10.1
pytest==6.2.2
pytest-cov==2.11.1
requests-mock==1.8.0
faker==5.0.2
sphinx==3.4.0
sphinx_rtd_theme==0.5.0
faker==6.3.0
sphinx==3.5.1
sphinx_rtd_theme==0.5.1
2 changes: 2 additions & 0 deletions sevenbridges/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
AppCopyStrategy, AppRawFormat, AsyncFileOperations, AsyncJobStates,
AutomationRunActions, DivisionRole, FileStorageType, ImportExportState,
TaskStatus, TransferState, VolumeAccessMode, VolumeType, PartSize,
AutomationStatus,
)
from sevenbridges.errors import (
SbgError, ResourceNotModified, ReadOnlyPropertyError, ValidationError,
Expand All @@ -67,6 +68,7 @@
'AsyncFileOperations', 'AsyncJobStates', 'AutomationRunActions',
'DivisionRole', 'FileStorageType', 'ImportExportState', 'TaskStatus',
'TransferState', 'VolumeAccessMode', 'VolumeType', 'PartSize',
'AutomationStatus',
# Errors
'SbgError', 'ResourceNotModified', 'ReadOnlyPropertyError',
'ValidationError', 'TaskValidationError', 'PaginationError', 'BadRequest',
Expand Down
8 changes: 8 additions & 0 deletions sevenbridges/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from requests.adapters import DEFAULT_POOLSIZE

from sevenbridges.errors import SbgError
from sevenbridges.http.client import HttpClient

from sevenbridges.models.app import App
Expand Down Expand Up @@ -61,6 +62,7 @@ def __init__(
pool_block=True, max_parallel_requests=100,
retry_count=RequestParameters.DEFAULT_RETRY_COUNT,
backoff_factor=RequestParameters.DEFAULT_BACKOFF_FACTOR,
debug=False,
):
"""
Initializes api object.
Expand All @@ -85,6 +87,12 @@ def __init__(
requests, only useful for multi thread applications.
:return: Api object instance.
"""
if not debug and url and url.startswith('http:'):
raise SbgError(
'Seven Bridges api client requires https, '
f'cannot initialize with url {url}'
)

super().__init__(
url=url, token=token, oauth_token=oauth_token, config=config,
timeout=timeout, proxies=proxies, error_handlers=error_handlers,
Expand Down
2 changes: 2 additions & 0 deletions sevenbridges/decorators.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copy
import logging
import functools
from json import JSONDecodeError
Expand Down Expand Up @@ -26,6 +27,7 @@ def wrapped(obj, *args, **kwargs):
if in_place and api_object:
obj._data = api_object._data
obj._dirty = api_object._dirty
obj._old = copy.deepcopy(api_object._data.data)
obj._data.fetched = False
return obj
elif api_object:
Expand Down
36 changes: 18 additions & 18 deletions sevenbridges/meta/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@

from sevenbridges.errors import ReadOnlyPropertyError, ValidationError

empty = object()


# noinspection PyProtectedMember
class Field:
EMPTY = object()

def __init__(self, name=None, read_only=True, validator=None):
self.name = name
self.read_only = read_only
self.validator = validator

def __set__(self, instance, value):
# using empty as sentinel, value can be only set once - first time
if self.read_only and instance._data[self.name] is not empty:
if self.read_only and instance._data[self.name] is not Field.EMPTY:
raise ReadOnlyPropertyError(
f'Property {self.name} is marked as read only!'
)
Expand Down Expand Up @@ -52,7 +52,7 @@ def validate(self, value):

# noinspection PyProtectedMember
class CompoundField(Field):
def __init__(self, cls, name=None, read_only=False, validator=None):
def __init__(self, cls, read_only, name=None, validator=None):
super().__init__(
name=name, read_only=read_only, validator=validator
)
Expand All @@ -61,44 +61,44 @@ def __init__(self, cls, name=None, read_only=False, validator=None):
def __get__(self, instance, owner):
data = instance._data[self.name]
# empty is used for read only fields, None all for others
if data is not empty and data is not None:
if data is not Field.EMPTY and data is not None:
return self.cls(api=instance._api, _parent=instance, **data)
else:
return None


# noinspection PyProtectedMember
class CompoundListField(Field):
def __init__(self, cls, name=None, read_only=True):
def __init__(self, cls, read_only, name=None):
super().__init__(name=name, read_only=read_only)
self.cls = cls

def __get__(self, instance, owner):
data = instance._data[self.name]
# empty is used for read only fields, None for all others
if data is not empty and data is not None:
if data is not Field.EMPTY and data is not None:
return [self.cls(api=instance._api, **item) for item in data]
else:
return []


class DictField(Field, dict):
def __init__(self, name=None, read_only=False):
def __init__(self, read_only, name=None):
super().__init__(name=name, read_only=read_only)


class HrefField(Field):
def __init__(self, name=None):
super().__init__(name=name)
def __init__(self, read_only, name=None):
super().__init__(name=name, read_only=read_only)


class ObjectIdField(Field):
def __init__(self, name=None, read_only=True):
def __init__(self, read_only, name=None):
super().__init__(name=name, read_only=read_only)


class IntegerField(Field):
def __init__(self, name=None, read_only=False):
def __init__(self, read_only, name=None):
super().__init__(name=name, read_only=read_only)

def validate(self, value):
Expand All @@ -110,7 +110,7 @@ def validate(self, value):


class FloatField(Field):
def __init__(self, name=None, read_only=False):
def __init__(self, read_only, name=None):
super().__init__(name=name, read_only=read_only)

def validate(self, value):
Expand All @@ -123,7 +123,7 @@ def validate(self, value):


class StringField(Field):
def __init__(self, name=None, read_only=False, max_length=None):
def __init__(self, read_only, name=None, max_length=None):
super().__init__(name=name, read_only=read_only)
self.max_length = max_length

Expand All @@ -139,7 +139,7 @@ def validate(self, value):


class DateTimeField(Field):
def __init__(self, name=None, read_only=True):
def __init__(self, read_only, name=None):
super().__init__(name=name, read_only=read_only)

def __get__(self, instance, cls):
Expand All @@ -154,7 +154,7 @@ def __get__(self, instance, cls):


class BooleanField(Field):
def __init__(self, name=None, read_only=False):
def __init__(self, read_only, name=None):
super().__init__(name=name, read_only=read_only)

def validate(self, value):
Expand All @@ -166,7 +166,7 @@ def validate(self, value):


class UuidField(Field):
def __init__(self, name=None, read_only=True):
def __init__(self, read_only, name=None):
super().__init__(name=name, read_only=read_only)

def validate(self, value):
Expand All @@ -181,7 +181,7 @@ def validate(self, value):


class BasicListField(Field):
def __init__(self, name=None, read_only=False, max_length=None):
def __init__(self, read_only, name=None, max_length=None):
super().__init__(name=name, read_only=read_only)
self.max_length = max_length

Expand Down
23 changes: 23 additions & 0 deletions sevenbridges/meta/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,30 @@ def modified_data(self):
# only return changed parameters even when metadata needs
# to be replaced
self._dirty['metadata'] = metadata

# Remove read only fields
read_only_fields = [
key for key in self._dirty
if getattr(self._fields.get(key, None), 'read_only', False)
]
for field in read_only_fields:
self._dirty.pop(field, None)

return self._dirty

def update_read_only(self, data):
# Set only read only fields
read_only_fields = [
key for key in data
if getattr(self._fields.get(key, None), 'read_only', False)
]
for field in read_only_fields:
self._data[field] = data[field]

# Clean dirty
self._dirty = {}
self._old = copy.deepcopy(self._data.data)

def equals(self, other):
if not type(other) == type(self):
return False
Expand All @@ -96,6 +118,7 @@ def deepcopy(self):
dct['equals'] = equals
dct['deepcopy'] = deepcopy
dct['_modified_data'] = modified_data
dct['_update_read_only'] = update_read_only

return type.__new__(mcs, name, bases, dct)

Expand Down
2 changes: 1 addition & 1 deletion sevenbridges/models/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class App(Resource):
AppRawFormat.YAML: 'application/yaml'
}

href = HrefField()
href = HrefField(read_only=True)
_id = StringField(read_only=True, name='id')
project = StringField(read_only=True)
name = StringField(read_only=True)
Expand Down
15 changes: 8 additions & 7 deletions sevenbridges/models/automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class AutomationPackage(Resource):
created_by = StringField(read_only=True)
created_on = DateTimeField(read_only=True)
archived = BooleanField(read_only=True)
custom_url = StringField()
custom_url = StringField(read_only=False)
memory_limit = IntegerField(read_only=False)

def __eq__(self, other):
Expand Down Expand Up @@ -200,13 +200,13 @@ class AutomationMember(Resource):
'get': '/automation/automations/{automation_id}/members/{id}',
}

href = HrefField()
href = HrefField(read_only=True)
id = StringField(read_only=True)
username = StringField(read_only=True)
email = StringField(read_only=True)
type = StringField(read_only=True)
name = StringField(read_only=True)
permissions = CompoundField(Permissions)
permissions = CompoundField(Permissions, read_only=False)

def __eq__(self, other):
if type(other) is not type(self):
Expand Down Expand Up @@ -323,6 +323,7 @@ class Automation(Resource):
"""
Central resource for managing automations.
"""
# noinspection PyProtectedMember
_URL = {
'query': '/automation/automations',
'get': '/automation/automations/{id}',
Expand All @@ -333,7 +334,7 @@ class Automation(Resource):
'restore': '/automation/automations/{automation_id}/actions/restore'
}

href = HrefField()
href = HrefField(read_only=True)
id = UuidField(read_only=True)
name = StringField(read_only=False)
description = StringField(read_only=False)
Expand Down Expand Up @@ -654,14 +655,14 @@ class AutomationRun(Resource):
'state': '/automation/runs/{id}/state',
}

href = HrefField()
href = HrefField(read_only=True)
id = StringField(read_only=True)
name = StringField(read_only=False)
automation = CompoundField(Automation, read_only=True)
package = CompoundField(AutomationPackage, read_only=True)
inputs = DictField()
inputs = DictField(read_only=False)
outputs = DictField(read_only=True)
settings = DictField()
settings = DictField(read_only=False)
created_on = DateTimeField(read_only=True)
start_time = DateTimeField(read_only=True)
end_time = DateTimeField(read_only=True)
Expand Down
2 changes: 1 addition & 1 deletion sevenbridges/models/billing_breakdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class BillingGroupBreakdown(Resource):
'get': '/billing/groups/{id}/breakdown'
}

href = HrefField()
href = HrefField(read_only=True)
project_breakdown = CompoundListField(ProjectBreakdown, read_only=True)
total_spending = CompoundField(Price, read_only=True)

Expand Down
4 changes: 2 additions & 2 deletions sevenbridges/models/billing_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class BillingGroup(Resource):
'query': '/billing/groups',
'get': '/billing/groups/{id}'
}
href = HrefField()
id = UuidField()
href = HrefField(read_only=True)
id = UuidField(read_only=True)
owner = StringField(read_only=True)
name = StringField(read_only=True)
type = StringField(read_only=True)
Expand Down
4 changes: 2 additions & 2 deletions sevenbridges/models/bulk.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class BulkRecord(Resource):
error = CompoundField(cls=Error)
resource = CompoundField(cls=Resource)
error = CompoundField(cls=Error, read_only=False)
resource = CompoundField(cls=Resource, read_only=False)

def __str__(self):
return f'<BulkRecord valid={self.valid}>'
Expand Down
2 changes: 1 addition & 1 deletion sevenbridges/models/compound/billing/project_breakdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ProjectBreakdown(Resource):
Project breakdown resource contains information regarding
billing group project breakdown costs.
"""
href = HrefField()
href = HrefField(read_only=True)
analysis_spending = CompoundField(Price, read_only=True)
task_breakdown = CompoundListField(TaskBreakdown, read_only=True)

Expand Down
2 changes: 1 addition & 1 deletion sevenbridges/models/compound/billing/task_breakdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class TaskBreakdown(Resource):
Task breakdown resource contains information regarding
billing group analysis breakdown costs.
"""
href = HrefField()
href = HrefField(read_only=True)
runner_username = StringField(read_only=True)
time_started = DateTimeField(read_only=True)
time_finished = DateTimeField(read_only=True)
Expand Down
2 changes: 1 addition & 1 deletion sevenbridges/models/compound/files/download_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class DownloadInfo(Resource):
"""
Download info resource contains download url for the file.
"""
url = HrefField()
url = HrefField(read_only=True)

def __str__(self):
return f'<DownloadInfo: url={self.url}>'
2 changes: 1 addition & 1 deletion sevenbridges/models/compound/limits/rate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Rate(Resource):
"""
limit = IntegerField(read_only=True)
remaining = IntegerField(read_only=True)
reset = DateTimeField()
reset = DateTimeField(read_only=True)

def __str__(self):
return f'<Rate: limit={self.limit}, remaining={self.remaining}>'

0 comments on commit f9952f6

Please sign in to comment.