salabim 24.0.11.post1__py3-none-any.whl → 24.0.13__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.
salabim/salabim.py CHANGED
@@ -1,13 +1,13 @@
1
- # _ _ _ ____ _ _ ___ _ _
2
- # ___ __ _ | | __ _ | |__ (_) _ __ ___ |___ \ | || | / _ \ / |/ |
3
- # / __| / _` || | / _` || '_ \ | || '_ ` _ \ __) || || |_ | | | | | || |
4
- # \__ \| (_| || || (_| || |_) || || | | | | | / __/ |__ _| _ | |_| | _ | || |
5
- # |___/ \__,_||_| \__,_||_.__/ |_||_| |_| |_| |_____| |_| (_) \___/ (_)|_||_|
1
+ # _ _ _ ____ _ _ ___ _ _____
2
+ # ___ __ _ | | __ _ | |__ (_) _ __ ___ |___ \ | || | / _ \ / ||___ /
3
+ # / __| / _` || | / _` || '_ \ | || '_ ` _ \ __) || || |_ | | | | | | |_ \
4
+ # \__ \| (_| || || (_| || |_) || || | | | | | / __/ |__ _| _ | |_| | _ | | ___) |
5
+ # |___/ \__,_||_| \__,_||_.__/ |_||_| |_| |_| |_____| |_| (_) \___/ (_)|_||____/
6
6
  # discrete event simulation
7
7
  #
8
8
  # see www.salabim.org for more information, the documentation and license information
9
9
 
10
- __version__ = "24.0.11"
10
+ __version__ = "24.0.13"
11
11
 
12
12
  import heapq
13
13
  import random
@@ -60,7 +60,7 @@ PyDroid = sys.platform == "linux" and any("pydroid" in v for v in os.environ.val
60
60
  PyPy = platform.python_implementation() == "PyPy"
61
61
  Chromebook = "penguin" in platform.uname()
62
62
  PythonInExcel = not ("__file__" in globals())
63
- AnacondaCode = sys.platform=="emscripten"
63
+ AnacondaCode = sys.platform == "emscripten"
64
64
 
65
65
 
66
66
  def a_log(*args):
@@ -154,8 +154,9 @@ nan = float("nan")
154
154
  if Pythonista or AnacondaCode or PythonInExcel:
155
155
  _yieldless = False
156
156
  else:
157
- _yieldless=True
158
-
157
+ _yieldless = True
158
+
159
+
159
160
  class QueueFullError(Exception):
160
161
  pass
161
162
 
@@ -4144,7 +4145,7 @@ if Pythonista:
4144
4145
  try:
4145
4146
  ims = scene.load_pil_image(capture_image)
4146
4147
  except SystemError:
4147
- im_file = "temp.png" # hack for Pythonista 3.4 ***
4148
+ im_file = "temp.png" # hack for Pythonista 3.4
4148
4149
  capture_image.save(im_file, "PNG")
4149
4150
  ims = scene.load_image_file(im_file)
4150
4151
  scene.image(ims, 0, 0, *capture_image.size)
@@ -5716,15 +5717,19 @@ class Queue:
5716
5717
 
5717
5718
 
5718
5719
  class Store(Queue):
5719
- def __init__(self, name: str = None, capacity: int = inf, env: "Environment" = None, *args, **kwargs):
5720
- super().__init__(name=name, capacity=capacity, env=env, *args, **kwargs)
5720
+ def __init__(self, name: str = None, monitor: Any = True, fill: Iterable = None, capacity: float = inf, env: "Environment" = None, *args, **kwargs) -> None:
5721
+ super().__init__(name=name, monitor=monitor, fill=None, capacity=capacity, env=env, *args, **kwargs)
5722
+
5721
5723
  with self.env.suppress_trace():
5722
5724
  self._to_store_requesters = Queue(f"{name}.to_store_requesters", env=env)
5723
5725
  self._to_store_requesters._isinternal = True
5724
5726
  self._from_store_requesters = Queue(f"{name}.from_store_requesters", env=env)
5725
5727
  self._from_store_requesters._isinternal = True
5726
5728
 
5727
- self.setup(*args, **kwargs)
5729
+ if fill is not None: # this cannot be done by Queue.__init__ as the requesters are not defined at that time
5730
+ with self.env.suppress_trace():
5731
+ for c in fill:
5732
+ c.enter(self)
5728
5733
 
5729
5734
  def set_capacity(self, cap: float) -> None:
5730
5735
  """
@@ -7977,6 +7982,10 @@ by adding:
7977
7982
  ----
7978
7983
  Only if yieldless is False: if to be used for the current component, use ``yield self.cancel()``.
7979
7984
  """
7985
+ if self.status.value == data:
7986
+ if self.env._trace:
7987
+ self.env.print_trace("", "", "cancel (on data component) " + self.name() + " " + self._modetxt())
7988
+ return
7980
7989
  if self.status.value != current:
7981
7990
  self._checkisnotdata()
7982
7991
  self._remove()
@@ -9044,10 +9053,8 @@ by adding:
9044
9053
  self.status._value = waiting
9045
9054
  self._reschedule(scheduled_time, schedule_priority, urgent, "wait", cap_now)
9046
9055
  else:
9047
- return # ***
9048
- if self.env._yieldless:
9049
- if self is self.env._current_component:
9050
- self.env._glet.switch()
9056
+ return
9057
+
9051
9058
 
9052
9059
  def _trywait(self):
9053
9060
  if self.status.value == interrupted:
@@ -9996,6 +10003,211 @@ by adding:
9996
10003
  return None
9997
10004
 
9998
10005
 
10006
+ class Event(Component):
10007
+ """
10008
+ Event object
10009
+
10010
+ An event object is a specialized Component that is usually not subclassed.
10011
+
10012
+ Apart from the usual Component parameters it has an action parameter, to specifies what should
10013
+ happen after becoming active. This action is usually a lambda function.
10014
+
10015
+ Parameters
10016
+ ----------
10017
+ action : callable
10018
+ function called when the component becomes current.
10019
+
10020
+ action_string : str
10021
+ string to be printed in trace when action gets executed (default: "action")
10022
+
10023
+ name : str
10024
+ name of the component.
10025
+
10026
+ if the name ends with a period (.),
10027
+ auto serializing will be applied
10028
+
10029
+ if the name end with a comma,
10030
+ auto serializing starting at 1 will be applied
10031
+
10032
+ if omitted, the name will be derived from the class
10033
+ it is defined in (lowercased)
10034
+
10035
+ at : float or distribution
10036
+ schedule time
10037
+
10038
+ if omitted, now is used
10039
+
10040
+ if distribution, the distribution is sampled
10041
+
10042
+ delay : float or distributiom
10043
+ schedule with a delay
10044
+
10045
+ if omitted, no delay
10046
+
10047
+ if distribution, the distribution is sampled
10048
+
10049
+ priority : float
10050
+ priority
10051
+
10052
+ default: 0
10053
+
10054
+ if a component has the same time on the event list, this component is sorted accoring to
10055
+ the priority.
10056
+
10057
+ urgent : bool
10058
+ urgency indicator
10059
+
10060
+ if False (default), the component will be scheduled
10061
+ behind all other components scheduled
10062
+ for the same time and priority
10063
+
10064
+ if True, the component will be scheduled
10065
+ in front of all components scheduled
10066
+ for the same time and priority
10067
+
10068
+ suppress_trace : bool
10069
+ suppress_trace indicator
10070
+
10071
+ if True, this component will be excluded from the trace
10072
+
10073
+ If False (default), the component will be traced
10074
+
10075
+ Can be queried or set later with the suppress_trace method.
10076
+
10077
+ suppress_pause_at_step : bool
10078
+ suppress_pause_at_step indicator
10079
+
10080
+ if True, if this component becomes current, do not pause when stepping
10081
+
10082
+ If False (default), the component will be paused when stepping
10083
+
10084
+ Can be queried or set later with the suppress_pause_at_step method.
10085
+
10086
+ skip_standby : bool
10087
+ skip_standby indicator
10088
+
10089
+ if True, after this component became current, do not activate standby components
10090
+
10091
+ If False (default), after the component became current activate standby components
10092
+
10093
+ Can be queried or set later with the skip_standby method.
10094
+
10095
+ mode : str preferred
10096
+ mode
10097
+
10098
+ will be used in trace and can be used in animations
10099
+
10100
+ if omitted, the mode will be "".
10101
+
10102
+ also mode_time will be set to now.
10103
+
10104
+ cap_now : bool
10105
+ indicator whether times (at, delay) in the past are allowed. If, so now() will be used.
10106
+ default: sys.default_cap_now(), usualy False
10107
+
10108
+ env : Environment
10109
+ environment where the component is defined
10110
+
10111
+ if omitted, default_env will be used
10112
+ """
10113
+
10114
+ def __init__(
10115
+ self,
10116
+ action: Callable,
10117
+ action_string="action",
10118
+ name: str = None,
10119
+ at: Union[float, Callable] = None,
10120
+ delay: Union[float, Callable] = None,
10121
+ priority: float = None,
10122
+ urgent: bool = None,
10123
+ suppress_trace: bool = False,
10124
+ suppress_pause_at_step: bool = False,
10125
+ skip_standby: bool = False,
10126
+ mode: str = "",
10127
+ cap_now: bool = None,
10128
+ env: "Environment" = None,
10129
+ **kwargs,
10130
+ ):
10131
+ self._action = action
10132
+ self._action_string = action_string
10133
+ self._action_taken = False
10134
+ if env is None:
10135
+ env = g.default_env
10136
+ super().__init__(
10137
+ name=name,
10138
+ at=at,
10139
+ delay=delay,
10140
+ priority=priority,
10141
+ urgent=urgent,
10142
+ suppress_trace=suppress_trace,
10143
+ suppress_pause_at_step=suppress_pause_at_step,
10144
+ skip_standby=skip_standby,
10145
+ mode=mode,
10146
+ cap_now=cap_now,
10147
+ env=env,
10148
+ process="process" if env._yieldless else "process_yield",
10149
+ **kwargs,
10150
+ )
10151
+
10152
+ def process_yield(self):
10153
+ self.env.print_trace("", "", self._action_string, "")
10154
+ self._action()
10155
+ self._action_taken = True
10156
+ return
10157
+ yield 1 # just to make it a generator
10158
+
10159
+ def process(self):
10160
+ self.env.print_trace("", "", self._action_string, "")
10161
+ self._action()
10162
+ self._action_taken = True
10163
+
10164
+ def action(self, value=None):
10165
+ """
10166
+ action
10167
+
10168
+ Parameters
10169
+ ----------
10170
+ value : callable
10171
+ new action callable
10172
+
10173
+ Returns
10174
+ -------
10175
+ current action : callable
10176
+ """
10177
+ if value is not None:
10178
+ self._action = value
10179
+ return self._action
10180
+
10181
+ def action_string(self, value=None):
10182
+ """
10183
+ action_string
10184
+
10185
+ Parameters
10186
+ ----------
10187
+ value : string
10188
+ new action_string
10189
+
10190
+ Returns
10191
+ -------
10192
+ current action_string : string
10193
+ """
10194
+
10195
+ if value is not None:
10196
+ self._action_string = value
10197
+ return self._action_string
10198
+
10199
+ def action_taken(self):
10200
+ """
10201
+ action_taken
10202
+
10203
+ Returns
10204
+ -------
10205
+ action taken: bool
10206
+ True if action has been taken, False if not
10207
+ """
10208
+ return self._action_taken
10209
+
10210
+
9999
10211
  class Environment:
10000
10212
  """
10001
10213
  environment object
@@ -10787,7 +10999,7 @@ class Environment:
10787
10999
  if c.overridden_lineno:
10788
11000
  self.print_trace(self.time_to_str(self._now - self._offset), c.name(), "current", s0=un_na(c.overridden_lineno))
10789
11001
  else:
10790
- self.print_trace(self.time_to_str(self._now - self._offset), c.name(), "current", s0=un_na(c.lineno_txt())) # ***
11002
+ self.print_trace(self.time_to_str(self._now - self._offset), c.name(), "current", s0=un_na(c.lineno_txt()))
10791
11003
  if c == self._main:
10792
11004
  self.running = False
10793
11005
  return
@@ -11502,7 +11714,7 @@ class Environment:
11502
11714
  if self._video_pingpong:
11503
11715
  self._images.extend(self._images[::-1])
11504
11716
  if self._video_repeat == 1: # in case of repeat == 1, loop should not be specified (otherwise, it might show twice)
11505
- if PythonInExcel or AnacondaCode: # ***
11717
+ if PythonInExcel or AnacondaCode:
11506
11718
  with b64_file_handler(self._video_name, mode="b", result=_pie_result) as f:
11507
11719
  self._images[0].save(
11508
11720
  f,
@@ -11523,7 +11735,7 @@ class Environment:
11523
11735
  optimize=False,
11524
11736
  )
11525
11737
  else:
11526
- if PythonInExcel or AnacondaCode: # ***
11738
+ if PythonInExcel or AnacondaCode:
11527
11739
  with b64_file_handler(self._video_name, mode="b", result=_pie_result) as f:
11528
11740
  self._images[0].save(
11529
11741
  f,
@@ -15308,8 +15520,6 @@ class Animate2dBase(DynamicClass):
15308
15520
 
15309
15521
  if not self.screen_coordinates:
15310
15522
  fontsize = fontsize * self.env._scale
15311
- # offsetx = offsetx * self.env._scale # ***
15312
- # offsety = offsety * self.env._scale # ***
15313
15523
  text_anchor = self.text_anchor(t)
15314
15524
 
15315
15525
  if self.attached_to:
@@ -15343,6 +15553,7 @@ class Animate2dBase(DynamicClass):
15343
15553
  qx = (x - self.env._x0) * self.env._scale
15344
15554
  qy = (y - self.env._y0) * self.env._scale
15345
15555
  max_lines = self.max_lines(t)
15556
+
15346
15557
  self._image_ident = (text, fontname, fontsize, angle, textcolor, max_lines)
15347
15558
  if self._image_ident != self._image_ident_prev:
15348
15559
  font, heightA = getfont(fontname, fontsize)
@@ -19818,6 +20029,13 @@ class ComponentGenerator(Component):
19818
20029
 
19819
20030
  e.g. env.main().activate()
19820
20031
 
20032
+ moments : iterable
20033
+ specifies the moments when the components have to be generated. It is not required that these are sorted.
20034
+
20035
+ note that the moments are specified in the current time unit
20036
+
20037
+ cannot be used together with at, delay, till, duration, number, iat,force_at, force_till, disturbance or equidistant
20038
+
19821
20039
  env : Environment
19822
20040
  environment where the component is defined
19823
20041
 
@@ -19846,6 +20064,7 @@ class ComponentGenerator(Component):
19846
20064
  disturbance: Callable = None,
19847
20065
  equidistant: bool = False,
19848
20066
  at_end: Callable = None,
20067
+ moments: Iterable = None,
19849
20068
  env: "Environment" = None,
19850
20069
  **kwargs,
19851
20070
  ):
@@ -19863,6 +20082,15 @@ class ComponentGenerator(Component):
19863
20082
 
19864
20083
  if not callable(component_class):
19865
20084
  raise ValueError("component_class must be a callable")
20085
+ if moments is not None:
20086
+ if any(prop for prop in (at, delay, till, duration, number, iat, force_at, force_till, disturbance, equidistant)):
20087
+ raise ValueError(
20088
+ "specifying at, delay, till,duration, number, iat,force_at, force_till, disturbance or equidistant is not allowed, if moments is specified"
20089
+ )
20090
+ if callable(moments):
20091
+ moments = moments()
20092
+ moments = sorted([env.spec_to_time(moment) for moment in moments])
20093
+
19866
20094
  self.component_class = component_class
19867
20095
  self.iat = iat
19868
20096
  self.disturbance = disturbance
@@ -19905,25 +20133,26 @@ class ComponentGenerator(Component):
19905
20133
  at = None
19906
20134
  process = ""
19907
20135
  else:
19908
- if self.iat is None and not equidistant:
19909
- if till == inf or self.number == inf:
19910
- raise ValueError("iat not specified --> till and number need to be specified")
19911
- if disturbance is not None:
19912
- raise ValueError("iat not specified --> disturbance not allowed")
19913
-
19914
- samples = sorted([Uniform(at, till)() for _ in range(self.number)])
19915
- if force_at or force_till:
19916
- if number == 1:
19917
- if force_at and force_till:
19918
- raise ValueError("force_at and force_till does not allow number=1")
19919
- samples = [at] if force_at else [till]
19920
- else:
19921
- v_at = at if force_at else samples[0]
19922
- v_till = till if force_till else samples[-1]
19923
- min_sample = samples[0]
19924
- max_sample = samples[-1]
19925
- samples = [interpolate(sample, min_sample, max_sample, v_at, v_till) for sample in samples]
19926
- self.intervals = [t1 - t0 for t0, t1 in zip([0] + samples, samples)]
20136
+ if (self.iat is None and not equidistant) or moments:
20137
+ if not moments:
20138
+ if till == inf or self.number == inf:
20139
+ raise ValueError("iat not specified --> till and number need to be specified")
20140
+ if disturbance is not None:
20141
+ raise ValueError("iat not specified --> disturbance not allowed")
20142
+
20143
+ moments = sorted([Uniform(at, till)() for _ in range(self.number)])
20144
+ if force_at or force_till:
20145
+ if number == 1:
20146
+ if force_at and force_till:
20147
+ raise ValueError("force_at and force_till does not allow number=1")
20148
+ moments = [at] if force_at else [till]
20149
+ else:
20150
+ v_at = at if force_at else moments[0]
20151
+ v_till = till if force_till else moments[-1]
20152
+ min_moment = moments[0]
20153
+ max_moment = moments[-1]
20154
+ moments = [interpolate(moment, min_moment, max_moment, v_at, v_till) for moment in moments]
20155
+ self.intervals = [t1 - t0 for t0, t1 in zip([0] + moments, moments)]
19927
20156
  at = self.intervals[0]
19928
20157
  self.intervals[0] = 0
19929
20158
  process = "do_spread_yieldless" if env._yieldless else "do_spread"
@@ -26312,7 +26541,7 @@ def fonts():
26312
26541
 
26313
26542
  for dir, recursive in dir_recursives:
26314
26543
  for file_path in dir.glob("**/*.*" if recursive else "*.*"):
26315
- if file_path.suffix.lower() == ".ttf": # ***
26544
+ if file_path.suffix.lower() == ".ttf":
26316
26545
  file = str(file_path)
26317
26546
  fn = os.path.basename(file).split(".")[0]
26318
26547
  if "_std_fonts" in globals() and fn in _std_fonts(): # test for availabiitly, because of minimized version
@@ -26372,7 +26601,8 @@ def getfont(fontname, fontsize):
26372
26601
  return getfont.lookup[(fontname, fontsize)]
26373
26602
  else:
26374
26603
  getfont.lookup = {}
26375
-
26604
+ if fontname=="":
26605
+ a=1
26376
26606
  if isinstance(fontname, str):
26377
26607
  fontlist1 = [fontname]
26378
26608
  else:
@@ -26405,7 +26635,8 @@ def getfont(fontname, fontsize):
26405
26635
  result = ImageFont.truetype(filename, size=int(fontsize))
26406
26636
  else:
26407
26637
  # refer to https://github.com/python-pillow/Pillow/issues/3730 for explanation (in order to load >= 500 fonts)
26408
- result = ImageFont.truetype(font=io.BytesIO(open(filename, "rb").read()), size=int(fontsize))
26638
+ with open(filename, "rb") as f:
26639
+ result = ImageFont.truetype(font=io.BytesIO(f.read()), size=int(fontsize))
26409
26640
  break
26410
26641
  except Exception:
26411
26642
  raise
@@ -26798,7 +27029,7 @@ class ImageContainer:
26798
27029
  if not (0 <= t_from < t_to):
26799
27030
  raise ValueError(f"animation_from={t_from} not with 0 and animation_to={t_to}")
26800
27031
  if t_to > self._duration:
26801
- raise ValueError(f"animation_to={t_to} > duration={duration}")
27032
+ raise ValueError(f"animation_to={t_to} > duration={self._duration}")
26802
27033
  if pingpong:
26803
27034
  interval = 2 * (t_to - t_from)
26804
27035
  else:
@@ -26944,7 +27175,7 @@ def reset() -> None:
26944
27175
  g.tkinter_loaded = "?"
26945
27176
  g.image_container_cache = {}
26946
27177
  g._default_cap_now = False
26947
- g._captured_stdout=[]
27178
+ g._captured_stdout = []
26948
27179
 
26949
27180
  random_seed() # always start with seed 1234567
26950
27181
 
@@ -27177,7 +27408,6 @@ reset()
27177
27408
  set_environment_aliases()
27178
27409
 
27179
27410
  if __name__ == "__main__":
27180
-
27181
27411
  sys.path.insert(0, str(Path(__file__).parent / ".." / "misc"))
27182
27412
  try:
27183
27413
  import salabim_exp
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: salabim
3
- Version: 24.0.11.post1
3
+ Version: 24.0.13
4
4
  Summary: salabim - discrete event simulation in Python
5
5
  Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
6
6
  Project-URL: Homepage, https://salabim.org
@@ -0,0 +1,10 @@
1
+ salabim/DejaVuSansMono.ttf,sha256=Z_oIXp5yp1Zaw2y2p3vaxwHhjHpG0MFbmwhxSh4aIEI,335068
2
+ salabim/LICENSE.txt,sha256=qHlBa-POyexatCxDTjSKMlYtkBFQDn9lu-YV_1L6V0U,1106
3
+ salabim/__init__.py,sha256=r7qPLvlmX0dkZDyjuTo8Jo3ex3sD1L4pmK6K5ib9vyw,56
4
+ salabim/calibri.ttf,sha256=RWpf8Uo31RfvGGNaSt9-2sXSuN87AVE_NFMRsV3LhBk,1330156
5
+ salabim/mplus-1m-regular.ttf,sha256=EuFHr90BJjuAn_r5MleJFN-WfkeWJ4tf7DweI5zr8tU,289812
6
+ salabim/salabim.py,sha256=NzhLkqRi8niqF9eQQPZRxJueQBROP0084q6qHrY6Qzc,1103953
7
+ salabim-24.0.13.dist-info/METADATA,sha256=-xWemNdEOZ-EbstXw49IqGKhr97mg85QxyuDLa2LtGA,3450
8
+ salabim-24.0.13.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
9
+ salabim-24.0.13.dist-info/top_level.txt,sha256=UE6zVlbi3F6T5ma1a_5TrojMaF21GYKDt9svvm0U4cQ,8
10
+ salabim-24.0.13.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (73.0.1)
2
+ Generator: setuptools (75.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,10 +0,0 @@
1
- salabim/DejaVuSansMono.ttf,sha256=Z_oIXp5yp1Zaw2y2p3vaxwHhjHpG0MFbmwhxSh4aIEI,335068
2
- salabim/LICENSE.txt,sha256=qHlBa-POyexatCxDTjSKMlYtkBFQDn9lu-YV_1L6V0U,1106
3
- salabim/__init__.py,sha256=r7qPLvlmX0dkZDyjuTo8Jo3ex3sD1L4pmK6K5ib9vyw,56
4
- salabim/calibri.ttf,sha256=RWpf8Uo31RfvGGNaSt9-2sXSuN87AVE_NFMRsV3LhBk,1330156
5
- salabim/mplus-1m-regular.ttf,sha256=EuFHr90BJjuAn_r5MleJFN-WfkeWJ4tf7DweI5zr8tU,289812
6
- salabim/salabim.py,sha256=8dANioKbD1covX06fZohLeNttQVbalrN6q8eODdZYTU,1097286
7
- salabim-24.0.11.post1.dist-info/METADATA,sha256=g39m6E1GfEa8lmL3Ji7vWbz2MpGvYClI1oedRWT2zQc,3456
8
- salabim-24.0.11.post1.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
9
- salabim-24.0.11.post1.dist-info/top_level.txt,sha256=UE6zVlbi3F6T5ma1a_5TrojMaF21GYKDt9svvm0U4cQ,8
10
- salabim-24.0.11.post1.dist-info/RECORD,,