mwxlib 0.95.9__py3-none-any.whl → 0.96.1__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 mwxlib might be problematic. Click here for more details.

mwx/controls.py CHANGED
@@ -12,7 +12,7 @@ from .utilus import funcall as _F
12
12
  from .framework import pack, Menu
13
13
 
14
14
  import numpy as np
15
- from numpy import nan, inf
15
+ from numpy import nan, inf # noqa: necessary to eval
16
16
 
17
17
 
18
18
  def _Tip(*tips):
@@ -97,10 +97,8 @@ class Param(object):
97
97
  v = self.std_value
98
98
  if v is None:
99
99
  return
100
- elif v == 'nan': v = nan
101
- elif v == 'inf': v = inf
102
- elif isinstance(v, str):
103
- v = self.__eval(v.replace(',', '')) # eliminates commas
100
+ if isinstance(v, str):
101
+ v = self.__eval(v.replace(',', '')) # eliminates commas; includes nan, inf
104
102
  self._set_value(v)
105
103
  if backcall:
106
104
  self.callback('control', self)
@@ -111,7 +109,8 @@ class Param(object):
111
109
  """
112
110
  if v is None:
113
111
  v = nan
114
- if v in (nan, inf):
112
+
113
+ if np.isnan(v) or np.isinf(v):
115
114
  self.__value = v
116
115
  for knob in self.knobs:
117
116
  knob.update_ctrl(None, notify=False)
@@ -185,7 +184,7 @@ class Param(object):
185
184
  @offset.setter
186
185
  def offset(self, v):
187
186
  if self.std_value is not None:
188
- if v is not nan: # Note: nan +x is not nan
187
+ if not np.isnan(v):
189
188
  v += self.std_value
190
189
  self._set_value(v)
191
190
 
@@ -253,6 +252,7 @@ class LParam(Param):
253
252
 
254
253
  @range.setter
255
254
  def range(self, v):
255
+ assert v is None or len(v) <= 3, "The range must be of length <= 3 or None"
256
256
  if v is None:
257
257
  v = (0, 0)
258
258
  self.__min = v[0]
@@ -266,7 +266,8 @@ class LParam(Param):
266
266
  """A knob index -> value
267
267
  Returns -1 if the value is nan or inf.
268
268
  """
269
- if self.value in (nan, inf):
269
+ v = self.value
270
+ if np.isnan(v) or np.isinf(v):
270
271
  return -1
271
272
  return int(round((self.value - self.min) / self.step))
272
273
 
@@ -319,7 +320,7 @@ class Knob(wx.Panel):
319
320
  wx.Panel.__init__(self, parent, **kwargs)
320
321
 
321
322
  assert isinstance(param, Param),\
322
- "Argument `param` must be an instance of Param class."
323
+ "Argument `param` must be an instance of Param"
323
324
 
324
325
  self.__bit = 1
325
326
  self.__par = param
mwx/framework.py CHANGED
@@ -1,7 +1,7 @@
1
1
  #! python3
2
2
  """mwxlib framework.
3
3
  """
4
- __version__ = "0.95.9"
4
+ __version__ = "0.96.1"
5
5
  __author__ = "Kazuya O'moto <komoto@jeol.co.jp>"
6
6
 
7
7
  from functools import wraps, partial
@@ -232,7 +232,7 @@ class KeyCtrlInterfaceMixin:
232
232
  state = self.handler.default_state
233
233
  event = keymap + ' pressed'
234
234
 
235
- assert state is not None, "Don't make keymap for None:state."
235
+ assert state is not None, "Don't make keymap for None:state"
236
236
 
237
237
  self.handler.update({ # DNA<KeyCtrlInterfaceMixin>
238
238
  state : {
@@ -1131,7 +1131,7 @@ class ShellFrame(MiniFrame):
1131
1131
  stdout=self.__shell.interp.stdout,
1132
1132
  skip=[Debugger.__module__, # Don't enter debugger
1133
1133
  EventMonitor.__module__, # Don't enter event-hook
1134
- ## FSM.__module__,
1134
+ FSM.__module__,
1135
1135
  'fnmatch', 'warnings', 'bdb', 'pdb',
1136
1136
  'wx.core', 'wx.lib.eventwatcher',
1137
1137
  ],
mwx/graphman.py CHANGED
@@ -28,7 +28,7 @@ from PIL.TiffImagePlugin import TiffImageFile
28
28
  from . import framework as mwx
29
29
  from .utilus import funcall as _F
30
30
  from .controls import ControlPanel, Icon
31
- from .framework import CtrlInterface, AuiNotebook
31
+ from .framework import CtrlInterface, AuiNotebook, Menu, FSM
32
32
 
33
33
  from .matplot2 import MatplotPanel # noqa
34
34
  from .matplot2g import GraphPlot
@@ -96,7 +96,7 @@ class Thread(object):
96
96
  try:
97
97
  self.handler = self.owner.handler
98
98
  except AttributeError:
99
- self.handler = mwx.FSM({ # DNA<Thread>
99
+ self.handler = FSM({ # DNA<Thread>
100
100
  None : {
101
101
  'thread_begin' : [ None ], # begin processing
102
102
  'thread_end' : [ None ], # end processing
@@ -263,10 +263,10 @@ class LayerInterface(CtrlInterface):
263
263
  ## thread_type = Thread
264
264
  thread = None
265
265
 
266
- ## layout helper function
266
+ ## layout helper function (deprecated: internal use only)
267
267
  pack = mwx.pack
268
268
 
269
- ## funcall = interactive_call
269
+ ## funcall = interactive_call (deprecated: internal use only)
270
270
  funcall = staticmethod(_F)
271
271
 
272
272
  ## for debug (internal use only)
@@ -384,7 +384,7 @@ class LayerInterface(CtrlInterface):
384
384
  lambda v: self.parent.inspect_plug(self.__module__)),
385
385
  ]
386
386
  self.Bind(wx.EVT_CONTEXT_MENU,
387
- lambda v: mwx.Menu.Popup(self, self.menu))
387
+ lambda v: Menu.Popup(self, self.menu))
388
388
 
389
389
  self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
390
390
  self.Bind(wx.EVT_SHOW, self.OnShow)
@@ -699,14 +699,15 @@ class Frame(mwx.Frame):
699
699
  lambda v: self.save_buffers_as_tiffs(),
700
700
  lambda v: v.Enable(self.__view.frame is not None)),
701
701
  (),
702
- ## ("Index", (
703
- ## (mwx.ID_(11), "&Import index\tCtrl+Shift+o", "Import index file", Icon('open'),
704
- ## lambda v: self.load_index()),
705
- ##
706
- ## (mwx.ID_(12), "&Export index\tCtrl+Shift+s", "Export index file", Icon('saveas'),
707
- ## lambda v: self.save_index(),
708
- ## lambda v: v.Enable(self.__view.frame is not None)),
709
- ## )),
702
+ ("Index", (
703
+ (mwx.ID_(11), "&Import index\tCtrl+Shift+o", "Import index file", Icon('open'),
704
+ lambda v: self.load_index()),
705
+
706
+ (mwx.ID_(12), "&Export index\tCtrl+Shift+s", "Export index file", Icon('saveas'),
707
+ lambda v: self.save_index(),
708
+ lambda v: v.Enable(self.__view.frame is not None)),
709
+ )),
710
+ (),
710
711
  ("Session", (
711
712
  (mwx.ID_(15), "&Open session", "Open session file",
712
713
  lambda v: self.load_session()),
@@ -1380,7 +1381,8 @@ class Frame(mwx.Frame):
1380
1381
  plug = _plug
1381
1382
  init(shell)
1382
1383
  self.shellframe.Show()
1383
- self.shellframe.load(plug)
1384
+ if wx.GetKeyState(wx.WXK_SHIFT):
1385
+ self.shellframe.load(plug)
1384
1386
 
1385
1387
  def OnLoadPlugins(self, evt):
1386
1388
  with wx.FileDialog(self, "Load a plugin file",
mwx/matplot2.py CHANGED
@@ -12,6 +12,7 @@ from matplotlib import cm
12
12
  import numpy as np
13
13
 
14
14
  from . import framework as mwx
15
+ from .framework import hotkey, regulate_key, pack, Menu, FSM
15
16
 
16
17
 
17
18
  ## state constants
@@ -111,7 +112,7 @@ class MatplotPanel(wx.Panel):
111
112
  self.infobar.Size = (0, 0) # workaround for incorrect wrap sizing
112
113
 
113
114
  self.SetSizer(
114
- mwx.pack(self, (
115
+ pack(self, (
115
116
  (self.canvas, 1, wx.EXPAND | wx.ALL, 0),
116
117
  (self.infobar, 0, wx.EXPAND | wx.ALL, 0),
117
118
  (self.modeline, 0, wx.EXPAND | wx.ALL, 2),
@@ -162,7 +163,7 @@ class MatplotPanel(wx.Panel):
162
163
  if self.handler.fork(self.handler.current_event, evt) is None:
163
164
  evt.Skip()
164
165
 
165
- self.__handler = mwx.FSM({ # DNA<MatplotPanel>
166
+ self.__handler = FSM({ # DNA<MatplotPanel>
166
167
  None : {
167
168
  'canvas_draw' : [ None, self.OnDraw ], # before canvas.draw
168
169
  #'canvas_drawn' : [ None, ], # after canvas.draw
@@ -397,6 +398,7 @@ class MatplotPanel(wx.Panel):
397
398
  self.modeline.SetBackgroundColour(self.selectedModeLineBg)
398
399
  self.modeline.SetForegroundColour(self.selectedModeLineFg)
399
400
  self.modeline.Refresh()
401
+ self.escape()
400
402
  evt.Skip()
401
403
 
402
404
  def on_focus_kill(self, evt): #<wx._core.FocusEvent>
@@ -404,6 +406,7 @@ class MatplotPanel(wx.Panel):
404
406
  self.modeline.SetBackgroundColour(self.unselectedModeLineBg)
405
407
  self.modeline.SetForegroundColour(self.unselectedModeLineFg)
406
408
  self.modeline.Refresh()
409
+ self.escape()
407
410
  evt.Skip()
408
411
 
409
412
  def escape(self, evt=None):
@@ -491,7 +494,7 @@ class MatplotPanel(wx.Panel):
491
494
  def on_menu(self, evt): #<matplotlib.backend_bases.MouseEvent>
492
495
  if self.__isMenu:
493
496
  self.canvas.SetFocus()
494
- mwx.Menu.Popup(self, self.menu)
497
+ Menu.Popup(self, self.menu)
495
498
  self.__isMenu = 0
496
499
 
497
500
  def on_pick(self, evt): #<matplotlib.backend_bases.PickEvent>
@@ -527,8 +530,8 @@ class MatplotPanel(wx.Panel):
527
530
 
528
531
  def on_hotkey_press(self, evt): #<wx._core.KeyEvent>
529
532
  """Called when a key is pressed."""
530
- key = mwx.hotkey(evt)
531
- self.__key = mwx.regulate_key(key + '+')
533
+ key = hotkey(evt)
534
+ self.__key = regulate_key(key + '+')
532
535
  if self.handler('{} pressed'.format(key), evt) is None:
533
536
  evt.Skip()
534
537
 
@@ -543,7 +546,7 @@ class MatplotPanel(wx.Panel):
543
546
 
544
547
  def on_hotkey_release(self, evt): #<wx._core.KeyEvent>
545
548
  """Called when a key is released."""
546
- key = mwx.hotkey(evt)
549
+ key = hotkey(evt)
547
550
  self.__key = ''
548
551
  if self.handler('{} released'.format(key), evt) is None:
549
552
  evt.Skip()
mwx/matplot2g.py CHANGED
@@ -10,10 +10,11 @@ from matplotlib import patches
10
10
  from PIL import Image
11
11
  import cv2
12
12
  import numpy as np
13
- from numpy import pi, nan
13
+ from numpy import pi
14
14
  from scipy import ndimage as ndi
15
15
 
16
16
  from . import framework as mwx
17
+ from .framework import Menu
17
18
  from .utilus import funcall as _F
18
19
  from .controls import Clipboard
19
20
  from .matplot2 import MatplotPanel
@@ -223,16 +224,18 @@ class AxesImagePhantom(object):
223
224
  @property
224
225
  def unit(self):
225
226
  """Logical length per pixel arb.unit [u/pixel]."""
226
- return self.__localunit or self.parent.globalunit
227
+ return self.__localunit or self.parent.unit
227
228
 
228
229
  @unit.setter
229
230
  def unit(self, v):
230
231
  u = self.unit
231
- if v in (None, nan):
232
- v = self.parent.globalunit
232
+ if v is None:
233
+ v = self.parent.unit
233
234
  self.__localunit = None
235
+ elif np.isnan(v) or np.isinf(v):
236
+ raise ValueError("The unit value cannot be NaN or Inf")
234
237
  elif v <= 0:
235
- raise Exception("The unit value must be greater than zero.")
238
+ raise ValueError("The unit value must be greater than zero")
236
239
  else:
237
240
  if v == self.__localunit: # no effect when v is localunit
238
241
  return
@@ -248,7 +251,7 @@ class AxesImagePhantom(object):
248
251
 
249
252
  @property
250
253
  def xy_unit(self):
251
- u = self.__localunit or self.parent.globalunit
254
+ u = self.__localunit or self.parent.unit
252
255
  return (u, u * self.__aspect_ratio)
253
256
 
254
257
  @property
@@ -583,7 +586,7 @@ class GraphPlot(MatplotPanel):
583
586
  lambda v: v.Check(self.frame is not None and self.frame.name == s))
584
587
 
585
588
  self.modeline.Bind(wx.EVT_CONTEXT_MENU, lambda v:
586
- mwx.Menu.Popup(self,
589
+ Menu.Popup(self,
587
590
  (_menu(j, art.name) for j, art in enumerate(self.__Arts))))
588
591
 
589
592
  self.modeline.Show(1)
@@ -827,10 +830,12 @@ class GraphPlot(MatplotPanel):
827
830
 
828
831
  @unit.setter
829
832
  def unit(self, v):
830
- if v in (None, nan):
831
- raise Exception("The globalunit must be non-nil value.")
833
+ if v is None:
834
+ raise ValueError("The globalunit must be non-nil value")
835
+ elif np.isnan(v) or np.isinf(v):
836
+ raise ValueError("Axis limits cannot be NaN or Inf")
832
837
  elif v <= 0:
833
- raise Exception("The unit value must be greater than zero.")
838
+ raise ValueError("The unit value must be greater than zero")
834
839
  else:
835
840
  if v == self.__unit: # no effect unless unit changes
836
841
  return
@@ -843,7 +848,7 @@ class GraphPlot(MatplotPanel):
843
848
  for art in self.__Arts:
844
849
  self.handler('frame_updated', art)
845
850
 
846
- globalunit = unit
851
+ globalunit = unit # for backward compatibility
847
852
 
848
853
  def update_markup_ratio(self, r):
849
854
  """Modify markup objects position."""
mwx/nutshell.py CHANGED
@@ -1986,7 +1986,7 @@ class EditorBook(AuiNotebook, CtrlInterface):
1986
1986
  self.swap_buffer(buf, lineno)
1987
1987
  return True
1988
1988
  ## return False
1989
- raise Exception("The requested URL was not found.")
1989
+ raise Exception("The requested URL was not found")
1990
1990
  if buf._load_file(filename):
1991
1991
  self.swap_buffer(buf, lineno)
1992
1992
  return True
@@ -106,8 +106,8 @@ class Plugin(Layer):
106
106
  updater=self.get_crop,
107
107
  )
108
108
 
109
- self.snp = Button(self, handler=self.snapshot, tip='Snapshot', icon='clock')
110
- self.exp = Button(self, handler=self.export, tip="Export", icon='save')
109
+ self.snp = Button(self, handler=self.snapshot, icon='clock')
110
+ self.exp = Button(self, handler=self.export, icon='save')
111
111
 
112
112
  self.rw = Button(self, handler=lambda v: self.seekdelta(-100), icon='|<-')
113
113
  self.fw = Button(self, handler=lambda v: self.seekdelta(+100), icon='->|')
mwx/utilus.py CHANGED
@@ -438,9 +438,8 @@ def find_modules(force=False, verbose=True):
438
438
  for info in walk_packages_no_import(['.']):
439
439
  _callback('.', info.name)
440
440
  else:
441
- print("Please wait a moment "
442
- "while Py{} gathers a list of all available modules... "
443
- "(This is executed once)".format(sys.winver))
441
+ print(f"Please wait a moment while Py{sys.winver} gathers a list of "
442
+ "all available modules... (This is executed once)")
444
443
 
445
444
  lm = list(sys.builtin_module_names)
446
445
 
@@ -684,7 +683,6 @@ class FSM(dict):
684
683
  " action : {}".format(typename(act)),
685
684
  " args : {}".format(args),
686
685
  " kwargs : {}".format(kwargs))
687
- traceback.print_exc()
688
686
  self.__matched_pattern = None
689
687
  return retvals
690
688
 
@@ -728,12 +726,11 @@ class FSM(dict):
728
726
 
729
727
  @staticmethod
730
728
  def dump(*args):
731
- print(*args, sep='\n', file=sys.__stderr__)
732
729
  fn = get_rootpath("deb-dump.log")
733
730
  with open(fn, 'a') as o:
734
731
  print(time.strftime('!!! %Y/%m/%d %H:%M:%S'), file=o)
735
- print(*args, sep='\n', end='\n', file=o)
736
- print(traceback.format_exc(), file=o)
732
+ print(*args, traceback.format_exc(), sep='\n', file=o)
733
+ print(*args, traceback.format_exc(), sep='\n', file=sys.__stderr__)
737
734
 
738
735
  @staticmethod
739
736
  def duplicate(context):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mwxlib
3
- Version: 0.95.9
3
+ Version: 0.96.1
4
4
  Summary: A wrapper of matplotlib and wxPython (phoenix)
5
5
  Home-page: https://github.com/komoto48g/mwxlib
6
6
  Author: Kazuya O'moto
@@ -1,28 +1,28 @@
1
1
  mwx/__init__.py,sha256=nN62CGTWjME7Zz2h-jIRB8MxwuErIkHPGrlBzydkF0o,643
2
2
  mwx/bookshelf.py,sha256=DAhMQk3_J4rdE50adBMFu5wNz3WdMh_zzJ37O9ncceo,5103
3
- mwx/controls.py,sha256=IAT3l3-azlPFtMc2VGNMiQhI390ExW0S4CgjEOKFI4s,48200
4
- mwx/framework.py,sha256=OVFv_5c-vrXRuvWm9YN8cHWmvN-svodmMAnlOAPBwQ4,75669
5
- mwx/graphman.py,sha256=NBe58KdAstFNPqyEmpPMMmWwjtNgJB7HumSI7-gjw54,70533
3
+ mwx/controls.py,sha256=2US02WAmYVydsPJ8ILzZ7opV83giX1P6kY5rr_ZTX2E,48278
4
+ mwx/framework.py,sha256=rzhushWI0BYuGeorx4M6ElKEtpMxrQIrlKOxqqEIokI,75665
5
+ mwx/graphman.py,sha256=4SRwCMs4-sS3n23FthythhuG9dBJ8TLz_TVyoYAbdoA,70639
6
6
  mwx/images.py,sha256=gyvqW4TLWdJMKmsaWiPiV_PuHJM1GbHgeELERLwGzg8,45291
7
- mwx/matplot2.py,sha256=1ISXVwqlu1EJKe5JXmBC2tuecsRoFzJrMZsqQMxdC50,33432
8
- mwx/matplot2g.py,sha256=KIuownEsF7t2RgHn3kOxjBXbKOdfS99X_CtQweG_FrM,65471
7
+ mwx/matplot2.py,sha256=-G7z0Osozm9NjLfXvX5UcdFviwbNUktjbd904_g-PqQ,33516
8
+ mwx/matplot2g.py,sha256=2vDx1pY89b4A7uaLd_RHK2HQ5hnwtH4C4wiYZLzuQjw,65707
9
9
  mwx/matplot2lg.py,sha256=pcu1oDPE_BlpFqNGPPKf9vpTuDs_h_u3OTRAJx5-Sds,27392
10
10
  mwx/mgplt.py,sha256=ITzxA97yDwr_35BUk5OqnyskSuKVDbpf2AQCKY1jHTI,5671
11
- mwx/nutshell.py,sha256=pbsQATwt906lOXGFWJmuwZIwU_ve78o46I2g5zNjvG4,137050
12
- mwx/utilus.py,sha256=Uwj6vbNUUztwOswPG75xtsT2y_PZqh3QiJraxmA9iT0,37401
11
+ mwx/nutshell.py,sha256=yQbih2Q1SXexJPuFNSwJnky5teRHXEKks12laHo6joo,137049
12
+ mwx/utilus.py,sha256=YShC4f67dIyjIOSZhSOEgBUfNR1nVOS6pxtm0GKMWrA,37320
13
13
  mwx/wxmon.py,sha256=6es-jVz9Ht7vZnG7VBJcaNYLHY0PnZtij60SXcZRTeY,12727
14
14
  mwx/wxpdb.py,sha256=lLowkkAgMhPFHAfklD7wZHq0qbSMjRxnBFtSajmVgME,19133
15
15
  mwx/wxwil.py,sha256=zP1-5Fpi1wpDQU977229zIH6QRuSkkyfuAlNKWjGoGg,5588
16
16
  mwx/wxwit.py,sha256=yU6XeCCWRBP7CLmpphjT072PfXAL30DNaxoChDX2p0I,7322
17
17
  mwx/plugins/__init__.py,sha256=jnJ-Sl9XJ_7BFDslD_r7dsbxsOT57q_IaEriV53XIGY,41
18
- mwx/plugins/ffmpeg_view.py,sha256=vUYNybIJsF1JGkDzjBgDyBQvDh8e1oKHlEMY5Fwc8L4,9399
18
+ mwx/plugins/ffmpeg_view.py,sha256=wZXRgsiPyIphcA8hvXQoE7NZp4cc_YBOO0ZpuKmUJKE,9369
19
19
  mwx/plugins/fft_view.py,sha256=HcnBr-y_yFdSfASPRzMLANta4SwSDShd65QWnCU3XhM,2665
20
20
  mwx/plugins/frame_listview.py,sha256=RaYOj-YKrpLqhT8TkBRDX1TQnSPv90V185j8OjrWJTs,10108
21
21
  mwx/plugins/line_profile.py,sha256=--9NIc3x5EfRB3L59JvD7rzENQHyiYfu7wWJo6AuMkA,820
22
22
  mwx/py/__init__.py,sha256=xykgfOytOwNuvXsfkLoumFZSTN-iBsHOjczYXngjmUE,12
23
23
  mwx/py/filling.py,sha256=KaHooM32hrGGgqw75Cbt8lAvACwC6RXadob9LGgNnEc,16806
24
- mwxlib-0.95.9.dist-info/LICENSE,sha256=PGtRKCaTkmUDlBQwpptJAxJtdqxIUtAmdBsaT9nUVkA,1091
25
- mwxlib-0.95.9.dist-info/METADATA,sha256=TpQuGm-jesDFI2I-g8nwPcIqT7Edpv4_pM1ZFBb3gwo,1925
26
- mwxlib-0.95.9.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
27
- mwxlib-0.95.9.dist-info/top_level.txt,sha256=SI1Mh118AstnUFGPNq5aMNKiAnVNmZk1S9Ij-OwAEpY,4
28
- mwxlib-0.95.9.dist-info/RECORD,,
24
+ mwxlib-0.96.1.dist-info/LICENSE,sha256=PGtRKCaTkmUDlBQwpptJAxJtdqxIUtAmdBsaT9nUVkA,1091
25
+ mwxlib-0.96.1.dist-info/METADATA,sha256=0D7iaLq2lSivCrt81_r6TINYUkXkU-tUAdsIVugNrrY,1925
26
+ mwxlib-0.96.1.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
27
+ mwxlib-0.96.1.dist-info/top_level.txt,sha256=SI1Mh118AstnUFGPNq5aMNKiAnVNmZk1S9Ij-OwAEpY,4
28
+ mwxlib-0.96.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (70.1.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5