Completed
Pull Request — master (#143)
by Jasper
01:27
created

JsonFile   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 194
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 194
rs 6
wmc 55

20 Methods

Rating   Name   Duplication   Size   Complexity  
B bySubject() 0 12 5
A byApproval() 0 7 4
A all() 0 11 2
A byId() 0 6 4
A __init__() 0 7 1
A latest() 0 5 2
A update() 0 11 3
B byLocation() 0 20 6
A knowsByLocation() 0 11 2
A add() 0 10 1
A serializeAndWrite() 0 3 1
A knows() 0 11 2
A updateApproval() 0 4 1
A byParents() 0 3 3
A byLocations() 0 2 3
A statistics() 0 7 3
A knowsSeries() 0 15 2
A dateTimeAdded() 0 2 1
B getSeries() 0 19 6
A inquire() 0 8 4

How to fix   Complexity   

Complex Class

Complex classes like JsonFile often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import os
2
from operator import itemgetter
3
from niprov.dependencies import Dependencies
4
5
6
class JsonFile(object):
7
    """Stores provenance in a local text file encoded as json.
8
    """
9
10
    def __init__(self, dependencies=Dependencies()):
11
        self.filesys = dependencies.getFilesystem()
12
        self.json = dependencies.getSerializer()
13
        self.factory = dependencies.getFileFactory()
14
        self.pictureCache = dependencies.getPictureCache()
15
        url = dependencies.getConfiguration().database_url
16
        self.datafile = os.path.expanduser(url)
17
18
    def serializeAndWrite(self, images):
19
        jsonstr = self.json.serializeList(images)
20
        self.filesys.write(self.datafile, jsonstr)
21
22
    def add(self, image):
23
        """Add the provenance for one file to storage.
24
25
        Args:
26
            image (:class:`.BaseFile`): Image file to store.
27
        """
28
        current = self.all()
29
        current.append(image)
30
        self.serializeAndWrite(current)
31
        self.pictureCache.saveToDisk(for_=image)
32
33
    def update(self, image):
34
        """Save changed provenance for this file..
35
36
        Args:
37
            image (:class:`.BaseFile`): Image file that has changed.
38
        """
39
        current = self.all()
40
        for r in range(len(current)):
41
            if current[r].location.toString() == image.location.toString():
42
                current[r] = image
43
        self.serializeAndWrite(current)
44
45
    def all(self):
46
        """Retrieve all known provenance from storage.
47
48
        Returns:
49
            list: List of provenance for known files.
50
        """
51
        try:
52
            jsonstr = self.filesys.read(self.datafile)
53
        except IOError:
54
            return []
55
        return self.json.deserializeList(jsonstr)
56
57
    def knowsByLocation(self, locationString):
58
        """Whether the file at this path has provenance associated with it.
59
60
        Returns:
61
            bool: True if provenance is available for that path.
62
        """
63
        try:
64
            self.byLocation(locationString)
65
        except IndexError:
66
            return False
67
        return True
68
69
    def knows(self, image):
70
        """Whether this file has provenance associated with it.
71
72
        Returns:
73
            bool: True if provenance is available for this image.
74
        """
75
        try:
76
            self.byLocation(image.path)
77
        except IndexError:
78
            return False
79
        return True
80
81
    def knowsSeries(self, image):
82
        """Whether the series that this file is part of has provenance 
83
        associated with it.
84
85
        Args:
86
            image (:class:`.BaseFile`): File for which the series is sought.
87
88
        Returns:
89
            bool: True if provenance is available for this series.
90
        """
91
        try:
92
            self.getSeries(image)
93
        except IndexError:
94
            return False
95
        return True
96
97
    def byLocation(self, locationString):
98
        """Get the provenance for a file at the given location. 
99
100
        In the case of a dicom series, this returns the provenance for the 
101
        series.
102
103
        Args:
104
            locationString (str): Location of the image file.
105
106
        Returns:
107
            dict: Provenance for one image file.
108
        """
109
        for image in self.all():
110
            if image.location.toString() == locationString:
111
                return image
112
            elif 'filesInSeries' in image.provenance and (
113
                locationString in image.provenance['filesInSeries']):
114
                return image
115
        else:
116
            raise IndexError('No file with that path known.')
117
118
    def byLocations(self, listOfLocations):
119
        return [f for f in self.all() if f.location.toString() in listOfLocations]
120
121
    def bySubject(self, subject):
122
        """Get the provenance for all files of a given participant. 
123
124
        Args:
125
            subject (str): The name or other ID string.
126
127
        Returns:
128
            list: List of provenance for known files imaging this subject.
129
        """
130
        all = self.all()
131
        imagesWithSubject = [f for f in all if 'subject' in f.provenance]
132
        return [f for f in imagesWithSubject if f.provenance['subject']==subject]
133
134
    def getSeries(self, image):
135
        """Get the object that carries provenance for the series that the image 
136
        passed is in. 
137
138
        Args:
139
            image (:class:`.DicomFile`): File that is part of a series.
140
141
        Returns:
142
            :class:`.DicomFile`: Image object that caries provenance for the series.
143
        """
144
        if image.getSeriesId() is None:
145
            raise IndexError('Image has no series id.')
146
        seriesId = image.getSeriesId()
147
        for image in self.all():
148
            if 'seriesuid' in image.provenance and (
149
                image.provenance['seriesuid'] == seriesId):
150
                return image
151
        else:
152
            raise IndexError('No provenance record for that series.')
153
154
    def byApproval(self, approvalStatus):
155
        matches = []
156
        for image in self.all():
157
            if 'approval' in image.provenance:
158
                if image.provenance['approval'] == approvalStatus:
159
                    matches.append(image)
160
        return matches
161
162
    def updateApproval(self, fpath, approvalStatus):
163
        img = self.byLocation(fpath)
164
        img.provenance['approval'] = approvalStatus
165
        self.update(img)
166
167
    def latest(self, n=20):
168
        def dateTimeAdded(img):
169
            return img.provenance.get('added')
170
        sortedImages = sorted(self.all(), key=dateTimeAdded, reverse=True)
171
        return sortedImages[:n]
172
173
    def statistics(self):
174
        stats = {}
175
        images = self.all()
176
        stats['count'] = len(images)
177
        sizes = [img.provenance['size'] for img in images if 'size' in img.provenance]
178
        stats['totalsize'] = sum(sizes)
179
        return stats
180
181
    def byId(self, uid):
182
        for image in self.all():
183
            if image.provenance['id'] == uid:
184
                return image
185
        else:
186
            raise IndexError('No file with that path known.')
187
188
    def byParents(self, listOfParentLocations):
189
        return [f for f in self.all() if set(listOfParentLocations).intersection(
190
            f.provenance.get('parents',[]))]
191
192
    def inquire(self, query):
193
        field = query.getFields()[0]
194
        matches = []
195
        for image in self.all():
196
            if field.name in image.provenance:
197
                if image.provenance[field.name] == field.value:
198
                    matches.append(image)
199
        return matches
200
201