qmenta-client 1.1.dev1504__py3-none-any.whl → 1.1.dev1507__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qmenta/client/Project.py +159 -138
- {qmenta_client-1.1.dev1504.dist-info → qmenta_client-1.1.dev1507.dist-info}/METADATA +1 -1
- {qmenta_client-1.1.dev1504.dist-info → qmenta_client-1.1.dev1507.dist-info}/RECORD +4 -4
- {qmenta_client-1.1.dev1504.dist-info → qmenta_client-1.1.dev1507.dist-info}/WHEEL +0 -0
qmenta/client/Project.py
CHANGED
|
@@ -231,7 +231,7 @@ class Project:
|
|
|
231
231
|
name="",
|
|
232
232
|
input_data_type="qmenta_medical_image_data:3.10",
|
|
233
233
|
add_to_container_id=0,
|
|
234
|
-
chunk_size=2
|
|
234
|
+
chunk_size=2**9,
|
|
235
235
|
split_data=False,
|
|
236
236
|
):
|
|
237
237
|
"""
|
|
@@ -262,14 +262,14 @@ class Project:
|
|
|
262
262
|
a power of 2: 2**x. Default value of x is 9 (chunk_size = 512 kB)
|
|
263
263
|
split_data : bool
|
|
264
264
|
If True, the platform will try to split the uploaded file into
|
|
265
|
-
different sessions. It will be ignored when the ssid
|
|
265
|
+
different sessions. It will be ignored when the ssid or a
|
|
266
|
+
add_to_container_id are given.
|
|
266
267
|
|
|
267
268
|
Returns
|
|
268
269
|
-------
|
|
269
270
|
bool
|
|
270
271
|
True if correctly uploaded, False otherwise.
|
|
271
272
|
"""
|
|
272
|
-
|
|
273
273
|
filename = os.path.split(file_path)[1]
|
|
274
274
|
input_data_type = "offline_analysis:1.0" if result else input_data_type
|
|
275
275
|
|
|
@@ -280,6 +280,8 @@ class Project:
|
|
|
280
280
|
|
|
281
281
|
total_bytes = os.path.getsize(file_path)
|
|
282
282
|
|
|
283
|
+
split_data = self.__assert_split_data(split_data, ssid, add_to_container_id)
|
|
284
|
+
|
|
283
285
|
# making chunks of the file and sending one by one
|
|
284
286
|
logger = logging.getLogger(logger_name)
|
|
285
287
|
with open(file_path, "rb") as file_object:
|
|
@@ -296,10 +298,6 @@ class Project:
|
|
|
296
298
|
response = None
|
|
297
299
|
last_chunk = False
|
|
298
300
|
|
|
299
|
-
if ssid and split_data:
|
|
300
|
-
logger.warning("split-data argument will be ignored because" + " ssid has been specified")
|
|
301
|
-
split_data = False
|
|
302
|
-
|
|
303
301
|
while True:
|
|
304
302
|
data = file_object.read(chunk_size)
|
|
305
303
|
if not data:
|
|
@@ -374,8 +372,7 @@ class Project:
|
|
|
374
372
|
logger.error(error)
|
|
375
373
|
return False
|
|
376
374
|
|
|
377
|
-
message = "Your data was successfully uploaded."
|
|
378
|
-
message += "The uploaded file will be soon processed !"
|
|
375
|
+
message = "Your data was successfully uploaded. The uploaded file will be soon processed !"
|
|
379
376
|
logger.info(message)
|
|
380
377
|
return True
|
|
381
378
|
|
|
@@ -477,7 +474,7 @@ class Project:
|
|
|
477
474
|
self._account.auth, "file_manager/download_file", data=params, stream=True
|
|
478
475
|
) as response, open(local_filename, "wb") as f:
|
|
479
476
|
|
|
480
|
-
for chunk in response.iter_content(chunk_size=2
|
|
477
|
+
for chunk in response.iter_content(chunk_size=2**9 * 1024):
|
|
481
478
|
f.write(chunk)
|
|
482
479
|
f.flush()
|
|
483
480
|
|
|
@@ -523,7 +520,7 @@ class Project:
|
|
|
523
520
|
self._account.auth, "file_manager/download_file", data=params, stream=True
|
|
524
521
|
) as response, open(zip_name, "wb") as f:
|
|
525
522
|
|
|
526
|
-
for chunk in response.iter_content(chunk_size=2
|
|
523
|
+
for chunk in response.iter_content(chunk_size=2**9 * 1024):
|
|
527
524
|
f.write(chunk)
|
|
528
525
|
f.flush()
|
|
529
526
|
|
|
@@ -2045,7 +2042,7 @@ class Project:
|
|
|
2045
2042
|
"""
|
|
2046
2043
|
Parse QC (Quality Control) text output into a structured dictionary format.
|
|
2047
2044
|
|
|
2048
|
-
This function takes raw QC text output (
|
|
2045
|
+
This function takes raw QC text output (from the Protocol Adherence analysis)
|
|
2049
2046
|
and parses it into a structured format that separates passed and failed rules,
|
|
2050
2047
|
along with their associated files and conditions.
|
|
2051
2048
|
|
|
@@ -2104,18 +2101,15 @@ class Project:
|
|
|
2104
2101
|
|
|
2105
2102
|
_, text = self.get_qc_status_subject(patient_id=patient_id, subject_name=subject_name, ssid=ssid)
|
|
2106
2103
|
|
|
2107
|
-
result = {
|
|
2108
|
-
"passed": [],
|
|
2109
|
-
"failed": []
|
|
2110
|
-
}
|
|
2104
|
+
result = {"passed": [], "failed": []}
|
|
2111
2105
|
|
|
2112
2106
|
# Split into failed and passed sections
|
|
2113
|
-
sections = re.split(r
|
|
2107
|
+
sections = re.split(r"={10,}\n\n", text)
|
|
2114
2108
|
if len(sections) == 3:
|
|
2115
|
-
failed_section = sections[1].split(
|
|
2109
|
+
failed_section = sections[1].split("=" * 10)[0].strip()
|
|
2116
2110
|
passed_section = sections[2].strip()
|
|
2117
2111
|
else:
|
|
2118
|
-
section = sections[1].split(
|
|
2112
|
+
section = sections[1].split("=" * 10)[0].strip()
|
|
2119
2113
|
if "PASSED QC MESSAGES" in section:
|
|
2120
2114
|
passed_section = section
|
|
2121
2115
|
failed_section = ""
|
|
@@ -2123,85 +2117,13 @@ class Project:
|
|
|
2123
2117
|
failed_section = section
|
|
2124
2118
|
passed_section = ""
|
|
2125
2119
|
|
|
2126
|
-
|
|
2127
|
-
failed_rules = re.split(r
|
|
2128
|
-
|
|
2129
|
-
rule_name = rule_text.split(' ❌')[0].strip()
|
|
2130
|
-
rule_data = {
|
|
2131
|
-
"rule": rule_name,
|
|
2132
|
-
"files": [],
|
|
2133
|
-
"failed_conditions": {}
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2136
|
-
# Extract all file comparisons for this rule
|
|
2137
|
-
file_comparisons = re.split(r'\t- Comparison with file:', rule_text)
|
|
2138
|
-
for comp in file_comparisons[1:]: # Skip first part
|
|
2139
|
-
file_name = comp.split('\n')[0].strip()
|
|
2140
|
-
conditions_match = re.search(
|
|
2141
|
-
r'Conditions:(.*?)(?=\n\t- Comparison|\n\n|$)',
|
|
2142
|
-
comp,
|
|
2143
|
-
re.DOTALL
|
|
2144
|
-
)
|
|
2145
|
-
if not conditions_match:
|
|
2146
|
-
continue
|
|
2147
|
-
|
|
2148
|
-
conditions_text = conditions_match.group(1).strip()
|
|
2149
|
-
# Parse conditions
|
|
2150
|
-
conditions = []
|
|
2151
|
-
for line in conditions_text.split('\n'):
|
|
2152
|
-
line = line.strip()
|
|
2153
|
-
if line.startswith('·'):
|
|
2154
|
-
status = '✔' if '✔' in line else '🚫'
|
|
2155
|
-
condition = re.sub(r'^· [✔🚫]\s*', '', line)
|
|
2156
|
-
conditions.append({
|
|
2157
|
-
"status": "passed" if status == '✔' else "failed",
|
|
2158
|
-
"condition": condition
|
|
2159
|
-
})
|
|
2160
|
-
|
|
2161
|
-
# Add to failed conditions summary
|
|
2162
|
-
for cond in conditions:
|
|
2163
|
-
if cond['status'] == 'failed':
|
|
2164
|
-
cond_text = cond['condition']
|
|
2165
|
-
if cond_text not in rule_data['failed_conditions']:
|
|
2166
|
-
rule_data['failed_conditions'][cond_text] = 0
|
|
2167
|
-
rule_data['failed_conditions'][cond_text] += 1
|
|
2168
|
-
|
|
2169
|
-
rule_data['files'].append({
|
|
2170
|
-
"file": file_name,
|
|
2171
|
-
"conditions": conditions
|
|
2172
|
-
})
|
|
2173
|
-
|
|
2174
|
-
result['failed'].append(rule_data)
|
|
2120
|
+
# Parse failed rules
|
|
2121
|
+
failed_rules = re.split(r"\n ❌ ", failed_section)
|
|
2122
|
+
result = self.__parse_fail_rules(failed_rules, result)
|
|
2175
2123
|
|
|
2176
2124
|
# Parse passed rules
|
|
2177
|
-
passed_rules = re.split(r
|
|
2178
|
-
|
|
2179
|
-
rule_name = rule_text.split(' ✅')[0].strip()
|
|
2180
|
-
rule_data = {
|
|
2181
|
-
"rule": rule_name,
|
|
2182
|
-
"sub_rule": None,
|
|
2183
|
-
"files": []
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
|
-
# Get sub-rule
|
|
2187
|
-
sub_rule_match = re.search(r'Sub-rule: (.*?)\n', rule_text)
|
|
2188
|
-
if sub_rule_match:
|
|
2189
|
-
rule_data['sub_rule'] = sub_rule_match.group(1).strip()
|
|
2190
|
-
|
|
2191
|
-
# Get files passed
|
|
2192
|
-
files_passed = re.search(r'List of files passed:(.*?)(?=\n\n|\Z)', rule_text, re.DOTALL)
|
|
2193
|
-
if files_passed:
|
|
2194
|
-
for line in files_passed.group(1).split('\n'):
|
|
2195
|
-
line = line.strip()
|
|
2196
|
-
if line.startswith('·'):
|
|
2197
|
-
file_match = re.match(r'· (.*?) \((\d+)/(\d+)\)', line)
|
|
2198
|
-
if file_match:
|
|
2199
|
-
rule_data['files'].append({
|
|
2200
|
-
"file": file_match.group(1).strip(),
|
|
2201
|
-
"passed_conditions": int(file_match.group(2)),
|
|
2202
|
-
})
|
|
2203
|
-
|
|
2204
|
-
result['passed'].append(rule_data)
|
|
2125
|
+
passed_rules = re.split(r"\n ✅ ", passed_section)
|
|
2126
|
+
result = self.__parse_pass_rules(passed_rules, result)
|
|
2205
2127
|
|
|
2206
2128
|
return result
|
|
2207
2129
|
|
|
@@ -2272,19 +2194,14 @@ class Project:
|
|
|
2272
2194
|
|
|
2273
2195
|
# Initialize statistics
|
|
2274
2196
|
stats = {
|
|
2275
|
-
|
|
2276
|
-
|
|
2197
|
+
"passed_rules": 0,
|
|
2198
|
+
"failed_rules": 0,
|
|
2277
2199
|
"subjects_passed": 0,
|
|
2278
2200
|
"subjects_with_failed": 0,
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
'failed': 0,
|
|
2284
|
-
'pass_percentage': 0.0
|
|
2285
|
-
},
|
|
2286
|
-
'condition_failure_rates': defaultdict(lambda: {'count': 0, 'percentage': 0.0}),
|
|
2287
|
-
'rule_success_rates': defaultdict(lambda: {'passed': 0, 'failed': 0, 'success_rate': 0.0}),
|
|
2201
|
+
"num_passed_files_distribution": defaultdict(int), # How many rules have N passed files
|
|
2202
|
+
"file_stats": {"total": 0, "passed": 0, "failed": 0, "pass_percentage": 0.0},
|
|
2203
|
+
"condition_failure_rates": defaultdict(lambda: {"count": 0, "percentage": 0.0}),
|
|
2204
|
+
"rule_success_rates": defaultdict(lambda: {"passed": 0, "failed": 0, "success_rate": 0.0}),
|
|
2288
2205
|
}
|
|
2289
2206
|
|
|
2290
2207
|
total_failures = 0
|
|
@@ -2294,56 +2211,56 @@ class Project:
|
|
|
2294
2211
|
# sum subjects with some failed qc message
|
|
2295
2212
|
stats["subjects_with_failed"] = sum([1 for rules in qc_results_list if rules["failed"]])
|
|
2296
2213
|
# sum rules that have passed
|
|
2297
|
-
stats["passed_rules"] = sum([len(rules[
|
|
2214
|
+
stats["passed_rules"] = sum([len(rules["passed"]) for rules in qc_results_list if rules["failed"]])
|
|
2298
2215
|
# sum rules that have failed
|
|
2299
|
-
stats["failed_rules"] = sum([len(rules[
|
|
2216
|
+
stats["failed_rules"] = sum([len(rules["failed"]) for rules in qc_results_list if rules["failed"]])
|
|
2300
2217
|
|
|
2301
2218
|
for qc_results in qc_results_list:
|
|
2302
2219
|
|
|
2303
2220
|
# Count passed files distribution
|
|
2304
|
-
for rule in qc_results[
|
|
2305
|
-
num_files = len(rule[
|
|
2306
|
-
stats[
|
|
2307
|
-
stats[
|
|
2308
|
-
stats[
|
|
2309
|
-
rule_name = rule[
|
|
2310
|
-
stats[
|
|
2311
|
-
|
|
2312
|
-
for rule in qc_results[
|
|
2313
|
-
stats[
|
|
2314
|
-
stats[
|
|
2315
|
-
for condition, count in rule[
|
|
2221
|
+
for rule in qc_results["passed"]:
|
|
2222
|
+
num_files = len(rule["files"])
|
|
2223
|
+
stats["num_passed_files_distribution"][num_files] += 1
|
|
2224
|
+
stats["file_stats"]["passed"] += len(rule["files"])
|
|
2225
|
+
stats["file_stats"]["total"] += len(rule["files"])
|
|
2226
|
+
rule_name = rule["rule"]
|
|
2227
|
+
stats["rule_success_rates"][rule_name]["passed"] += 1
|
|
2228
|
+
|
|
2229
|
+
for rule in qc_results["failed"]:
|
|
2230
|
+
stats["file_stats"]["total"] += len(rule["files"])
|
|
2231
|
+
stats["file_stats"]["failed"] += len(rule["files"])
|
|
2232
|
+
for condition, count in rule["failed_conditions"].items():
|
|
2316
2233
|
# Extract just the condition text without actual value
|
|
2317
|
-
clean_condition = re.sub(r
|
|
2318
|
-
stats[
|
|
2234
|
+
clean_condition = re.sub(r"\.\s*Actual value:.*$", "", condition)
|
|
2235
|
+
stats["condition_failure_rates"][clean_condition]["count"] += count
|
|
2319
2236
|
total_failures += count
|
|
2320
|
-
rule_name = rule[
|
|
2321
|
-
stats[
|
|
2237
|
+
rule_name = rule["rule"]
|
|
2238
|
+
stats["rule_success_rates"][rule_name]["failed"] += 1
|
|
2322
2239
|
|
|
2323
|
-
if stats[
|
|
2324
|
-
stats[
|
|
2325
|
-
(stats[
|
|
2240
|
+
if stats["file_stats"]["total"] > 0:
|
|
2241
|
+
stats["file_stats"]["pass_percentage"] = round(
|
|
2242
|
+
(stats["file_stats"]["passed"] / stats["file_stats"]["total"]) * 100, 2
|
|
2326
2243
|
)
|
|
2327
2244
|
|
|
2328
2245
|
# Calculate condition failure percentages
|
|
2329
|
-
for condition in stats[
|
|
2246
|
+
for condition in stats["condition_failure_rates"]:
|
|
2330
2247
|
if total_failures > 0:
|
|
2331
|
-
stats[
|
|
2332
|
-
(stats[
|
|
2248
|
+
stats["condition_failure_rates"][condition]["percentage"] = round(
|
|
2249
|
+
(stats["condition_failure_rates"][condition]["count"] / total_failures) * 100, 2
|
|
2333
2250
|
)
|
|
2334
2251
|
|
|
2335
2252
|
# Calculate rule success rates
|
|
2336
|
-
for rule in stats[
|
|
2337
|
-
total = stats[
|
|
2253
|
+
for rule in stats["rule_success_rates"]:
|
|
2254
|
+
total = stats["rule_success_rates"][rule]["passed"] + stats["rule_success_rates"][rule]["failed"]
|
|
2338
2255
|
if total > 0:
|
|
2339
|
-
stats[
|
|
2340
|
-
(stats[
|
|
2256
|
+
stats["rule_success_rates"][rule]["success_rate"] = round(
|
|
2257
|
+
(stats["rule_success_rates"][rule]["passed"] / total) * 100, 2
|
|
2341
2258
|
)
|
|
2342
2259
|
|
|
2343
2260
|
# Convert defaultdict to regular dict for cleaner JSON output
|
|
2344
|
-
stats[
|
|
2345
|
-
stats[
|
|
2346
|
-
stats[
|
|
2261
|
+
stats["num_passed_files_distribution"] = dict(stats["num_passed_files_distribution"])
|
|
2262
|
+
stats["condition_failure_rates"] = dict(stats["condition_failure_rates"])
|
|
2263
|
+
stats["rule_success_rates"] = dict(stats["rule_success_rates"])
|
|
2347
2264
|
|
|
2348
2265
|
return stats
|
|
2349
2266
|
|
|
@@ -2673,3 +2590,107 @@ class Project:
|
|
|
2673
2590
|
value.replace(d_type + ";", "")
|
|
2674
2591
|
file_metadata[d_tag] = {"operation": "in-list", "value": value.replace(d_type + ";", "").split(";")}
|
|
2675
2592
|
return modality, tags, file_metadata
|
|
2593
|
+
|
|
2594
|
+
def __assert_split_data(self, split_data, ssid, add_to_container_id):
|
|
2595
|
+
"""
|
|
2596
|
+
Assert if the split_data parameter is possible to use in regards
|
|
2597
|
+
to the ssid and add_to_container_id parameters during upload.
|
|
2598
|
+
Changes its status to False if needed.
|
|
2599
|
+
|
|
2600
|
+
Parameters
|
|
2601
|
+
----------
|
|
2602
|
+
split_data : Bool
|
|
2603
|
+
split_data parameter from method 'upload_file'.
|
|
2604
|
+
ssid : str
|
|
2605
|
+
Session ID.
|
|
2606
|
+
add_to_container_id : int or bool
|
|
2607
|
+
Container ID or False
|
|
2608
|
+
|
|
2609
|
+
Returns
|
|
2610
|
+
-------
|
|
2611
|
+
split_data : Bool
|
|
2612
|
+
|
|
2613
|
+
"""
|
|
2614
|
+
|
|
2615
|
+
logger = logging.getLogger(logger_name)
|
|
2616
|
+
if ssid and split_data:
|
|
2617
|
+
logger.warning("split-data argument will be ignored because ssid has been specified")
|
|
2618
|
+
split_data = False
|
|
2619
|
+
|
|
2620
|
+
if add_to_container_id and split_data:
|
|
2621
|
+
logger.warning("split-data argument will be ignored because add_to_container_id has been specified")
|
|
2622
|
+
split_data = False
|
|
2623
|
+
|
|
2624
|
+
return split_data
|
|
2625
|
+
|
|
2626
|
+
def __parse_fail_rules(self, failed_rules, result):
|
|
2627
|
+
"""
|
|
2628
|
+
Parse fail rules.
|
|
2629
|
+
"""
|
|
2630
|
+
|
|
2631
|
+
for rule_text in failed_rules[1:]: # Skip first empty part
|
|
2632
|
+
rule_name = rule_text.split(" ❌")[0].strip()
|
|
2633
|
+
rule_data = {"rule": rule_name, "files": [], "failed_conditions": {}}
|
|
2634
|
+
|
|
2635
|
+
# Extract all file comparisons for this rule
|
|
2636
|
+
file_comparisons = re.split(r"\t- Comparison with file:", rule_text)
|
|
2637
|
+
for comp in file_comparisons[1:]: # Skip first part
|
|
2638
|
+
file_name = comp.split("\n")[0].strip()
|
|
2639
|
+
conditions_match = re.search(r"Conditions:(.*?)(?=\n\t- Comparison|\n\n|$)", comp, re.DOTALL)
|
|
2640
|
+
if not conditions_match:
|
|
2641
|
+
continue
|
|
2642
|
+
|
|
2643
|
+
conditions_text = conditions_match.group(1).strip()
|
|
2644
|
+
# Parse conditions
|
|
2645
|
+
conditions = []
|
|
2646
|
+
for line in conditions_text.split("\n"):
|
|
2647
|
+
line = line.strip()
|
|
2648
|
+
if line.startswith("·"):
|
|
2649
|
+
status = "✔" if "✔" in line else "🚫"
|
|
2650
|
+
condition = re.sub(r"^· [✔🚫]\s*", "", line)
|
|
2651
|
+
conditions.append({"status": "passed" if status == "✔" else "failed", "condition": condition})
|
|
2652
|
+
|
|
2653
|
+
# Add to failed conditions summary
|
|
2654
|
+
for cond in conditions:
|
|
2655
|
+
if cond["status"] == "failed":
|
|
2656
|
+
cond_text = cond["condition"]
|
|
2657
|
+
if cond_text not in rule_data["failed_conditions"]:
|
|
2658
|
+
rule_data["failed_conditions"][cond_text] = 0
|
|
2659
|
+
rule_data["failed_conditions"][cond_text] += 1
|
|
2660
|
+
|
|
2661
|
+
rule_data["files"].append({"file": file_name, "conditions": conditions})
|
|
2662
|
+
|
|
2663
|
+
result["failed"].append(rule_data)
|
|
2664
|
+
return result
|
|
2665
|
+
|
|
2666
|
+
def __parse_pass_rules(self, passed_rules, result):
|
|
2667
|
+
"""
|
|
2668
|
+
Parse pass rules.
|
|
2669
|
+
"""
|
|
2670
|
+
|
|
2671
|
+
for rule_text in passed_rules[1:]: # Skip first empty part
|
|
2672
|
+
rule_name = rule_text.split(" ✅")[0].strip()
|
|
2673
|
+
rule_data = {"rule": rule_name, "sub_rule": None, "files": []}
|
|
2674
|
+
|
|
2675
|
+
# Get sub-rule
|
|
2676
|
+
sub_rule_match = re.search(r"Sub-rule: (.*?)\n", rule_text)
|
|
2677
|
+
if sub_rule_match:
|
|
2678
|
+
rule_data["sub_rule"] = sub_rule_match.group(1).strip()
|
|
2679
|
+
|
|
2680
|
+
# Get files passed
|
|
2681
|
+
files_passed = re.search(r"List of files passed:(.*?)(?=\n\n|\Z)", rule_text, re.DOTALL)
|
|
2682
|
+
if files_passed:
|
|
2683
|
+
for line in files_passed.group(1).split("\n"):
|
|
2684
|
+
line = line.strip()
|
|
2685
|
+
if line.startswith("·"):
|
|
2686
|
+
file_match = re.match(r"· (.*?) \((\d+)/(\d+)\)", line)
|
|
2687
|
+
if file_match:
|
|
2688
|
+
rule_data["files"].append(
|
|
2689
|
+
{
|
|
2690
|
+
"file": file_match.group(1).strip(),
|
|
2691
|
+
"passed_conditions": int(file_match.group(2)),
|
|
2692
|
+
}
|
|
2693
|
+
)
|
|
2694
|
+
|
|
2695
|
+
result["passed"].append(rule_data)
|
|
2696
|
+
return result
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
qmenta/__init__.py,sha256=ED6jHcYiuYpr_0vjGz0zx2lrrmJT9sDJCzIljoDfmlM,65
|
|
2
2
|
qmenta/client/Account.py,sha256=7BOWHtRbHdfpBYQqv9v2m2Fag13pExZSxFsjDA7UsW0,9500
|
|
3
3
|
qmenta/client/File.py,sha256=iCrzrd7rIfjjW2AgMgUoK-ZF2wf-95wCcPKxKw6PGyg,4816
|
|
4
|
-
qmenta/client/Project.py,sha256=
|
|
4
|
+
qmenta/client/Project.py,sha256=pV9mW90BzPIMOFauPiAONBIsYJfGsbi_Xabbe9DW32U,100493
|
|
5
5
|
qmenta/client/Subject.py,sha256=b5sg9UFtn11bmPM-xFXP8aehOm_HGxnhgT7IPKbrZnE,8688
|
|
6
6
|
qmenta/client/__init__.py,sha256=Mtqe4zf8n3wuwMXSALENQgp5atQY5VcsyXWs2hjBs28,133
|
|
7
7
|
qmenta/client/utils.py,sha256=vWUAW0r9yDetdlwNo86sdzKn03FNGvwa7D9UtOA3TEc,2419
|
|
8
|
-
qmenta_client-1.1.
|
|
9
|
-
qmenta_client-1.1.
|
|
10
|
-
qmenta_client-1.1.
|
|
8
|
+
qmenta_client-1.1.dev1507.dist-info/METADATA,sha256=hGQLiIggQT0NX8tOF8VIklAu9VdsOSldflacTNBIo9g,672
|
|
9
|
+
qmenta_client-1.1.dev1507.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
|
10
|
+
qmenta_client-1.1.dev1507.dist-info/RECORD,,
|
|
File without changes
|