squad 1.74__py3-none-any.whl → 1.75__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
squad/api/rest.py CHANGED
@@ -1400,7 +1400,6 @@ class FailuresWithConfidenceSerializer(TestSerializer):
1400
1400
  'has_known_issues',
1401
1401
  'result',
1402
1402
  'url',
1403
- 'suite',
1404
1403
  )
1405
1404
 
1406
1405
 
@@ -100,9 +100,14 @@ class Backend(BaseBackend):
100
100
 
101
101
  ('BUILD', 'linaro@anders', '1yPYGaOEPNwr2pCqBgONY43zORq')
102
102
 
103
+ The leading string determines the type of the tuxsuite object:
104
+ - BUILD
105
+ - OEBUILD
106
+ - TEST
107
+
103
108
  """
104
109
 
105
- regex = r'^(BUILD|TEST):([0-9a-z_\-]+@[0-9a-z_\-]+)#([a-zA-Z0-9]+)$'
110
+ regex = r'^(OEBUILD|BUILD|TEST):([0-9a-z_\-]+@[0-9a-z_\-]+)#([a-zA-Z0-9]+)$'
106
111
  matches = re.findall(regex, job_id)
107
112
  if len(matches) == 0:
108
113
  raise FetchIssue(f'Job id "{job_id}" does not match "{regex}"')
@@ -113,18 +118,19 @@ class Backend(BaseBackend):
113
118
  def generate_job_id(self, result_type, result):
114
119
  """
115
120
  The job id for TuxSuite results is generated using 3 pieces of info:
116
- 1. If it's either "BUILD" or "TEST" result;
121
+ 1. If it's either "BUILD", "OEBUILD" or "TEST" result;
117
122
  2. The TuxSuite project. Ex: "linaro/anders"
118
123
  3. The ksuid of the object. Ex: "1yPYGaOEPNwr2pfqBgONY43zORp"
119
124
 
120
125
  A couple examples for job_id are:
121
126
  - BUILD:linaro@anders#1yPYGaOEPNwr2pCqBgONY43zORq
127
+ - OEBUILD:linaro@lkft#2Wetiz7Qs0TbtfPgPT7hUObWqDK
122
128
  - TEST:arm@bob#1yPYGaOEPNwr2pCqBgONY43zORp
123
129
 
124
130
  Then it's up to SQUAD's TuxSuite backend to parse the job_id
125
131
  and fetch results properly.
126
132
  """
127
- _type = "TEST" if result_type == "test" else "BUILD"
133
+ _type = result_type.upper()
128
134
  project = result["project"].replace("/", "@")
129
135
  uid = result["uid"]
130
136
  return f"{_type}:{project}#{uid}"
@@ -257,6 +263,30 @@ class Backend(BaseBackend):
257
263
 
258
264
  return status, completed, metadata, tests, metrics, logs
259
265
 
266
+ def parse_oebuild_results(self, test_job, job_url, results, settings):
267
+ required_keys = ['download_url', 'result']
268
+ self.__check_required_keys__(required_keys, results)
269
+
270
+ # Make metadata
271
+ metadata_keys = settings.get('OEBUILD_METADATA_KEYS', [])
272
+ metadata = {k: results.get(k) for k in metadata_keys}
273
+ metadata['job_url'] = job_url
274
+ metadata['job_id'] = test_job.job_id
275
+
276
+ sources = results.get('sources')
277
+ if sources:
278
+ metadata['sources'] = sources
279
+
280
+ # Create tests and metrics
281
+ tests = {}
282
+ metrics = {}
283
+ completed = True
284
+ status = 'Complete'
285
+ tests['build/build'] = 'pass' if results['result'] == 'pass' else 'fail'
286
+ logs = self.fetch_url(results['download_url'], 'build.log').text
287
+
288
+ return status, completed, metadata, tests, metrics, logs
289
+
260
290
  def parse_test_results(self, test_job, job_url, results, settings):
261
291
  status = 'Complete'
262
292
  completed = True
squad/ci/models.py CHANGED
@@ -4,6 +4,7 @@ import traceback
4
4
  import yaml
5
5
  from io import StringIO
6
6
  from django.db import models, transaction, DatabaseError
7
+ from django.db.models import Q
7
8
  from django.utils import timezone
8
9
  from dateutil.relativedelta import relativedelta
9
10
 
@@ -66,6 +67,19 @@ class Backend(models.Model):
66
67
  yield test_job
67
68
 
68
69
  def fetch(self, job_id):
70
+ # Job statuses can be one of:
71
+ # * None
72
+ # * Submitted
73
+ # * Scheduling
74
+ # * Scheduled
75
+ # * Running
76
+ # * Complete
77
+ # * Incomplete
78
+ # * Canceled
79
+ # * Fetching
80
+ # Only jobs in 'Complete', 'Canceled' and 'Incomplete' are eligible for fetching
81
+
82
+ job_status = None
69
83
  with transaction.atomic():
70
84
  try:
71
85
  test_job = TestJob.objects.select_for_update(nowait=True).get(pk=job_id)
@@ -91,6 +105,8 @@ class Backend(models.Model):
91
105
  test_job.save()
92
106
  return
93
107
 
108
+ job_status = test_job.job_status
109
+ test_job.job_status = 'Fetching'
94
110
  test_job.fetched = True
95
111
  test_job.fetched_at = timezone.now()
96
112
  test_job.save()
@@ -130,10 +146,16 @@ class Backend(models.Model):
130
146
  except DuplicatedTestJob as exception:
131
147
  logger.error('Failed to fetch test_job(%d): "%s"' % (test_job.id, str(exception)))
132
148
 
149
+ if test_job.testrun:
150
+ self.__postprocess_testjob__(test_job)
151
+
152
+ # Removed the 'Fetching' job_status only after eventual plugins
153
+ # are finished, this garantees extra tests and metadata to
154
+ # be in SQUAD before the build is considered finished
155
+ test_job.job_status = job_status
133
156
  test_job.save()
134
157
 
135
158
  if test_job.testrun:
136
- self.__postprocess_testjob__(test_job)
137
159
  UpdateProjectStatus()(test_job.testrun)
138
160
 
139
161
  def __postprocess_testjob__(self, test_job):
@@ -177,9 +199,16 @@ class Backend(models.Model):
177
199
  return '%s (%s)' % (self.name, self.implementation_type)
178
200
 
179
201
 
202
+ class TestJobManager(models.Manager):
203
+
204
+ def pending(self):
205
+ return self.filter(Q(fetched=False) | Q(job_status='Fetching'))
206
+
207
+
180
208
  class TestJob(models.Model):
181
209
 
182
210
  __test__ = False
211
+ objects = TestJobManager()
183
212
 
184
213
  # input - internal
185
214
  backend = models.ForeignKey(Backend, related_name='test_jobs', on_delete=models.CASCADE)
squad/core/models.py CHANGED
@@ -584,10 +584,11 @@ class Build(models.Model):
584
584
  def important_metadata(self):
585
585
  wanted = (self.project.important_metadata_keys or '').splitlines()
586
586
  m = self.metadata
587
+ metadata = self.metadata
587
588
  if len(wanted):
588
- return {k: m[k] for k in wanted if k in m}
589
- else:
590
- return self.metadata
589
+ metadata = {k: m[k] for k in wanted if k in m}
590
+
591
+ return metadata
591
592
 
592
593
  @property
593
594
  def has_extra_metadata(self):
@@ -619,7 +620,7 @@ class Build(models.Model):
619
620
  # dependency on squad.ci, what in theory violates our architecture.
620
621
  testjobs = self.test_jobs
621
622
  if testjobs.count() > 0:
622
- if testjobs.filter(fetched=False).count() > 0:
623
+ if testjobs.pending().count() > 0:
623
624
  # a build that has pending CI jobs is NOT finished
624
625
  reasons.append("There are unfinished CI jobs")
625
626
  else:
squad/frontend/views.py CHANGED
@@ -6,6 +6,9 @@ from django.core.paginator import Paginator, EmptyPage
6
6
  from django.contrib.auth.decorators import login_required
7
7
  from django.http import HttpResponse, Http404
8
8
  from django.shortcuts import render, get_object_or_404, redirect, reverse
9
+ from django.utils import timezone
10
+
11
+ from dateutil.relativedelta import relativedelta
9
12
 
10
13
  from squad.ci.models import TestJob
11
14
  from squad.core.models import Group, Metric, ProjectStatus, Status, MetricThreshold, KnownIssue, Test
@@ -83,24 +86,30 @@ def home(request):
83
86
  return render(request, 'squad/index.jinja2', context)
84
87
 
85
88
 
86
- def group_home(request, group_slug):
87
- group = get_object_or_404(Group, slug=group_slug)
88
-
89
- projects_queryset = group.projects.accessible_to(request.user)
89
+ def get_project_list(group, user, order_by, display_all_projects):
90
+ projects_queryset = group.projects.accessible_to(user)
90
91
  projects_queryset = projects_queryset.annotate(latest_build_id=Max('builds__id'))
91
92
 
92
- if request.user.is_authenticated:
93
- projects_queryset = projects_queryset.prefetch_related(Prefetch('subscriptions', queryset=Subscription.objects.filter(user=request.user), to_attr='user_subscriptions'))
93
+ if user.is_authenticated:
94
+ projects_queryset = projects_queryset.prefetch_related(Prefetch('subscriptions', queryset=Subscription.objects.filter(user=user), to_attr='user_subscriptions'))
94
95
 
95
- order_by = request.GET.get('order', 'last_updated')
96
96
  if group.get_setting('SORT_PROJECTS_BY_NAME'):
97
97
  order_by = 'by_name'
98
98
 
99
99
  elif order_by == 'last_updated':
100
100
  projects_queryset = projects_queryset.order_by('-datetime')
101
101
 
102
- display_all_projects = request.GET.get('all_projects') is not None
103
102
  num_projects = group.get_setting('DEFAULT_PROJECT_COUNT')
103
+
104
+ show_projects_active_n_days_ago = group.get_setting('SHOW_PROJECTS_ACTIVE_N_DAYS_AGO')
105
+ if show_projects_active_n_days_ago:
106
+ earilest_timestamp = timezone.now() - relativedelta(days=show_projects_active_n_days_ago)
107
+ latest_project_count = projects_queryset.filter(datetime__gte=earilest_timestamp).count()
108
+ if latest_project_count > 0:
109
+ projects_queryset = projects_queryset.filter(datetime__gte=earilest_timestamp)
110
+ # Ignore DEFAULT_PROJECT_COUNT if we are using age of project
111
+ num_projects = None
112
+
104
113
  if display_all_projects or num_projects is None:
105
114
  display_all_projects = True
106
115
  projects = projects_queryset.all()
@@ -108,6 +117,16 @@ def group_home(request, group_slug):
108
117
  display_all_projects = projects_queryset.count() <= num_projects
109
118
  projects = projects_queryset.all()[:num_projects]
110
119
 
120
+ return projects
121
+
122
+
123
+ def group_home(request, group_slug):
124
+ group = get_object_or_404(Group, slug=group_slug)
125
+
126
+ order_by = request.GET.get('order', 'last_updated')
127
+ display_all_projects = request.GET.get('all_projects') is not None
128
+ projects = get_project_list(group, request.user, order_by, display_all_projects)
129
+
111
130
  has_archived_projects = False
112
131
  latest_build_ids = {}
113
132
  projects_count = 0
@@ -157,7 +176,7 @@ def project_home(request, group_slug, project_slug):
157
176
  builds = [b for b in __get_builds_with_status__(project, 11)]
158
177
  last_build = len(builds) and builds[0] or None
159
178
 
160
- metadata = last_build and sorted(last_build.important_metadata.items()) or ()
179
+ metadata = last_build and last_build.important_metadata.items() or ()
161
180
  context = {
162
181
  'project': project,
163
182
  'builds': builds,
@@ -379,7 +398,7 @@ def build(request, group_slug, project_slug, version):
379
398
  'build': build,
380
399
  'test_results': test_results,
381
400
  'results_layout': results_layout,
382
- 'metadata': sorted(build.important_metadata.items()),
401
+ 'metadata': build.important_metadata.items(),
383
402
  'has_extra_metadata': build.has_extra_metadata,
384
403
  'failures_only': failures_only,
385
404
  'testjobs_progress': testjobs_progress,
squad/settings.py CHANGED
@@ -81,6 +81,14 @@ except ImportError:
81
81
  pass
82
82
 
83
83
 
84
+ django_allauth_middleware = None
85
+ try:
86
+ import allauth.account.middleware # noqa: F401
87
+ django_allauth_middleware = 'allauth.account.middleware.AccountMiddleware'
88
+ except ImportError:
89
+ pass
90
+
91
+
84
92
  __apps__ = [
85
93
  'django.contrib.admin',
86
94
  'django.contrib.auth',
@@ -146,6 +154,7 @@ __middlewares__ = [
146
154
  'django.contrib.messages.middleware.MessageMiddleware',
147
155
  'django.middleware.clickjacking.XFrameOptionsMiddleware',
148
156
  django_toolbar_middleware, # OPTIONAL
157
+ django_allauth_middleware,
149
158
  ]
150
159
 
151
160
  MIDDLEWARE = [middleware for middleware in __middlewares__ if middleware]
squad/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '1.74'
1
+ __version__ = '1.75'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: squad
3
- Version: 1.74
3
+ Version: 1.75
4
4
  Summary: Software Quality Dashboard
5
5
  Home-page: https://github.com/Linaro/squad
6
6
  Author: Antonio Terceiro
@@ -7,17 +7,17 @@ squad/http.py,sha256=KuIKtpf3yOvf5fwc0T2MR0ul1l4AKxq3b0CLdk6KBhM,3667
7
7
  squad/jinja2.py,sha256=OKX-lzNz6qtTZL56HWv4UBMPuBl4WQXv0qFJztGp9zs,2541
8
8
  squad/mail.py,sha256=xH5wuIpD7u1fTN9vNOcbzByojleaffsKwp-9i3BeOD0,390
9
9
  squad/manage.py,sha256=Z-LXT67p0R-IzwJ9fLIAacEZmU0VUjqDOSg7j2ZSxJ4,1437
10
- squad/settings.py,sha256=8IuKnLnrgaTcotaLA-LIbDFVgmAiPqzMQfl5h3F0-58,14097
10
+ squad/settings.py,sha256=6pEhWkjPgdRS4H2u3BKkPQJGs7oFy8Nbgk3KfhMR7hk,14328
11
11
  squad/socialaccount.py,sha256=vySqPwQ3qVVpahuJ-Snln8K--yzRL3bw4Nx27AsB39A,789
12
12
  squad/urls.py,sha256=JiEfVW8YlzLPE52c2aHzdn5kVVKK4o22w8h5KOA6QhQ,2776
13
- squad/version.py,sha256=hk3bRYhpJeAKv63C_jpKjb80t-uOh4QIP5iQeZZmnU4,21
13
+ squad/version.py,sha256=OC6GKMpskxHfSEgiJ0ItR5gLS6QXMDmVsSkmSs2egUw,21
14
14
  squad/wsgi.py,sha256=SF8T0cQ0OPVyuYjO5YXBIQzvSXQHV0M2BTmd4gP1rPs,387
15
15
  squad/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  squad/api/apps.py,sha256=Trk72p-iV1uGn0o5mdJn5HARUoHGbfgO49jwXvpkmdQ,141
17
17
  squad/api/ci.py,sha256=g9ZodQW3A1scNci3K3EGprxSyW3rL9xn-qBYxd-_8ns,6462
18
18
  squad/api/data.py,sha256=obKDV0-neEvj5lPF9VED2gy_hpfhGtLJABYvSY38ing,2379
19
19
  squad/api/filters.py,sha256=Zvp8DCJmiNquFWqvfVseEAAMYYPiT95RUjqKdzcqSnw,6917
20
- squad/api/rest.py,sha256=d9KBEjX7ru8MZqOWptIwMnC1t_kmm6Tqp1xv61LntUE,75953
20
+ squad/api/rest.py,sha256=EfpWljAvkkDwurRNoR1H6hRtYjpIS0B4kzXsQgS4UW8,75932
21
21
  squad/api/urls.py,sha256=rmsdaL1uOCVSZ5x1redup9RliICmijaBjRK5ObsTkG8,1343
22
22
  squad/api/utils.py,sha256=Sa8QFId3_oSqD2UOoY3Kuh54LLDLPNMq2sub5ktd6Fs,1160
23
23
  squad/api/views.py,sha256=yGLUp6RtNI5vuae6cOStMuUpSia46LcEVam3eMXmEqY,3885
@@ -25,14 +25,14 @@ squad/ci/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  squad/ci/admin.py,sha256=7yB-6F0cvt0NVvzGOTlZCyGPV_YHarmbKJZTTzataT4,2255
26
26
  squad/ci/apps.py,sha256=6OVnzTdJkxdqEJnKWYE9dZgUcc29_T1LrDw41cK4EQk,139
27
27
  squad/ci/exceptions.py,sha256=a1sccygniTYDSQi7FRn_6doapddFFiMf55AwGUh5Y80,227
28
- squad/ci/models.py,sha256=RoXJY4Xgu3-YA4FtcK-kem8HR8paqOb5648NByAlzDo,12325
28
+ squad/ci/models.py,sha256=R8kdrbkcplABeTZPSWADL2dOQsWbSfgfOh5jZltKK-8,13265
29
29
  squad/ci/tasks.py,sha256=yrtxfPuYEqqGCDYRwLz2XyIp9a7LJ-K6Zm3VS1ymdZk,2728
30
30
  squad/ci/utils.py,sha256=38zHpw8xkZDSFlkG-2BwSK6AkcddK9OkN9LXuQ3SHR0,97
31
31
  squad/ci/backend/__init__.py,sha256=yhpotXT9F4IdAOXvGQ3-17eOHAFwoaqf9SnMX17ab30,534
32
32
  squad/ci/backend/fake.py,sha256=zzOXGesDCW9xiMQvXGD_jqCQF32yEd7hPM8DgfZxUok,2159
33
33
  squad/ci/backend/lava.py,sha256=5KEXFwKGjzXPPe7_rzMGF7YShYCLwmooTc8tQUpGBSM,33076
34
34
  squad/ci/backend/null.py,sha256=vP77Xj7roruehSjX8fJs7xK2aWxgaUA2id3P8nHNrEY,4949
35
- squad/ci/backend/tuxsuite.py,sha256=wILo_rUfIYtL3yfctr-uX8eMUmcfJdfq15xEEXVM64o,15552
35
+ squad/ci/backend/tuxsuite.py,sha256=yAdLBOC3uLL2-wYxCsd8Rnk6lzTQLHcl2UOUjjJHYBg,16606
36
36
  squad/ci/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  squad/ci/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  squad/ci/management/commands/create_tuxsuite_boot_tests.py,sha256=JvjNusebLX71eyz9d-kaeCyekYSpzc1eXoeIqWK9ygo,4045
@@ -80,7 +80,7 @@ squad/core/comparison.py,sha256=LR3-Unv0CTmakFCDzF_h8fm2peTJzkv79mQWNau1iwI,2442
80
80
  squad/core/data.py,sha256=2zw56v7iYRTUc7wlhuUNgwIIMmK2w84hi-amR9J7EPU,2236
81
81
  squad/core/failures.py,sha256=X6lJVghM2fOrd-RfuHeLlezW2pt7owDZ8eX-Kn_Qrt0,918
82
82
  squad/core/history.py,sha256=APIgJ1fXAGyxoNgxVMn02kJzXhLR1x2SG4UyCcTyUEQ,3467
83
- squad/core/models.py,sha256=wJ-0e5JNPxBendEMOk9YdxLH03tvikDAeGjzDcuSCxw,60343
83
+ squad/core/models.py,sha256=E3E78ftWCYEqBz3ePyNf1wM5lfYRotjqdchfyWMZpRs,60346
84
84
  squad/core/notification.py,sha256=rOpO6F63w7_5l9gQgWBBEk-MFBjp7x_hVzoVIVyDze0,10030
85
85
  squad/core/plugins.py,sha256=PzgKrRvnwoe4ynLh0aRGAsL0CngHSu8dose37CO5ESg,5992
86
86
  squad/core/queries.py,sha256=78fhIJZWXIlDryewYAt96beK1VJad66Ufu8cg3dHh4w,7698
@@ -306,7 +306,7 @@ squad/frontend/tests.py,sha256=q64Z6DyS6TiJTCzF6SXw4wZfvXR8c0A4t-f0ib1jJ0w,8713
306
306
  squad/frontend/urls.py,sha256=biWauxwXR5j9kOfrSUqkv1Iqz-elB2aNViS9_UFoLzQ,4882
307
307
  squad/frontend/user_settings.py,sha256=IBogLusn-WNQiXqc_cF23s_tAWZ6wYVL6bbCfah_Aco,4189
308
308
  squad/frontend/utils.py,sha256=DeH58CJUI1dovpQrj3a-DcxNzM0cxsnBDOF0mrC4Qws,1364
309
- squad/frontend/views.py,sha256=n4Xll_gRBHOLV1pki6DZpXgkYsNGWhtiu2fruGEZ9iI,25695
309
+ squad/frontend/views.py,sha256=wK7ZPD-qjdhNTaBNJAZAwYmRTmIaHdjRxLiD6Oe9k1w,26477
310
310
  squad/frontend/locale/django.pot,sha256=ENSJCBcxS2gWg5byPCMxU8gvTNR6qtJdsSi9emUUOvY,30178
311
311
  squad/frontend/locale/pl/LC_MESSAGES/django.po,sha256=yKoZEDP72mn_5WSNDP8zh5Pl3Z5Z9OGuoDG159NPvEI,36297
312
312
  squad/frontend/locale/pt/LC_MESSAGES/django.po,sha256=8srHMXuXV0ZuUM284LEwTb5sXWEMzlCmul1lKXsDZc8,36375
@@ -428,9 +428,9 @@ squad/run/__main__.py,sha256=DOl8JOi4Yg7DdtwnUeGqtYBJ6P2k-D2psAEuYOjWr8w,66
428
428
  squad/run/listener.py,sha256=jBeOQhPGb4EdIREB1QsCzYuumsfJ-TqJPd3nR-0m59g,200
429
429
  squad/run/scheduler.py,sha256=CDJG3q5C0GuQuxwlMOfWTSSJpDdwbR6rzpbJfuA0xuw,277
430
430
  squad/run/worker.py,sha256=jtML0h5qKDuSbpJ6_rpWP4MT_rsGA7a24AhwGxBquzk,594
431
- squad-1.74.dist-info/COPYING,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
432
- squad-1.74.dist-info/METADATA,sha256=6eAK6LZ4Cba9SY_SyjKNnlGeg-aB-pepP-slkiBz0pk,1245
433
- squad-1.74.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
434
- squad-1.74.dist-info/entry_points.txt,sha256=apCDQydHZtvqV334ql6NhTJUAJeZRdtAm0TVcbbAi5Q,194
435
- squad-1.74.dist-info/top_level.txt,sha256=_x9uqE1XppiiytmVTl_qNgpnXus6Gsef69HqfliE7WI,6
436
- squad-1.74.dist-info/RECORD,,
431
+ squad-1.75.dist-info/COPYING,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
432
+ squad-1.75.dist-info/METADATA,sha256=tnDoktc6-u7r6X5n5HGdAk1sxjAQYXptjBuWrtSjN_w,1245
433
+ squad-1.75.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
434
+ squad-1.75.dist-info/entry_points.txt,sha256=apCDQydHZtvqV334ql6NhTJUAJeZRdtAm0TVcbbAi5Q,194
435
+ squad-1.75.dist-info/top_level.txt,sha256=_x9uqE1XppiiytmVTl_qNgpnXus6Gsef69HqfliE7WI,6
436
+ squad-1.75.dist-info/RECORD,,
File without changes
File without changes