squad 1.74__py3-none-any.whl → 1.75__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.

Potentially problematic release.


This version of squad might be problematic. Click here for more details.

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