maya-umbrella 0.12.1__py2.py3-none-any.whl → 0.13.0__py2.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 maya-umbrella might be problematic. Click here for more details.

@@ -1 +1 @@
1
- __version__ = "0.12.1"
1
+ __version__ = "0.13.0"
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2015-2016 Markus Unterwaditzer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,229 @@
1
+ import contextlib
2
+ import io
3
+ import os
4
+ import sys
5
+ import tempfile
6
+
7
+ try:
8
+ import fcntl
9
+ except ImportError:
10
+ fcntl = None
11
+
12
+ # `fspath` was added in Python 3.6
13
+ try:
14
+ from os import fspath
15
+ except ImportError:
16
+ fspath = None
17
+
18
+ __version__ = '1.4.1'
19
+
20
+
21
+ PY2 = sys.version_info[0] == 2
22
+
23
+ text_type = unicode if PY2 else str # noqa
24
+
25
+
26
+ def _path_to_unicode(x):
27
+ if not isinstance(x, text_type):
28
+ return x.decode(sys.getfilesystemencoding())
29
+ return x
30
+
31
+
32
+ DEFAULT_MODE = "wb" if PY2 else "w"
33
+
34
+
35
+ _proper_fsync = os.fsync
36
+
37
+
38
+ if sys.platform != 'win32':
39
+ if hasattr(fcntl, 'F_FULLFSYNC'):
40
+ def _proper_fsync(fd):
41
+ # https://lists.apple.com/archives/darwin-dev/2005/Feb/msg00072.html
42
+ # https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/fsync.2.html
43
+ # https://github.com/untitaker/python-atomicwrites/issues/6
44
+ fcntl.fcntl(fd, fcntl.F_FULLFSYNC)
45
+
46
+ def _sync_directory(directory):
47
+ # Ensure that filenames are written to disk
48
+ fd = os.open(directory, 0)
49
+ try:
50
+ _proper_fsync(fd)
51
+ finally:
52
+ os.close(fd)
53
+
54
+ def _replace_atomic(src, dst):
55
+ os.rename(src, dst)
56
+ _sync_directory(os.path.normpath(os.path.dirname(dst)))
57
+
58
+ def _move_atomic(src, dst):
59
+ os.link(src, dst)
60
+ os.unlink(src)
61
+
62
+ src_dir = os.path.normpath(os.path.dirname(src))
63
+ dst_dir = os.path.normpath(os.path.dirname(dst))
64
+ _sync_directory(dst_dir)
65
+ if src_dir != dst_dir:
66
+ _sync_directory(src_dir)
67
+ else:
68
+ from ctypes import windll, WinError
69
+
70
+ _MOVEFILE_REPLACE_EXISTING = 0x1
71
+ _MOVEFILE_WRITE_THROUGH = 0x8
72
+ _windows_default_flags = _MOVEFILE_WRITE_THROUGH
73
+
74
+ def _handle_errors(rv):
75
+ if not rv:
76
+ raise WinError()
77
+
78
+ def _replace_atomic(src, dst):
79
+ _handle_errors(windll.kernel32.MoveFileExW(
80
+ _path_to_unicode(src), _path_to_unicode(dst),
81
+ _windows_default_flags | _MOVEFILE_REPLACE_EXISTING
82
+ ))
83
+
84
+ def _move_atomic(src, dst):
85
+ _handle_errors(windll.kernel32.MoveFileExW(
86
+ _path_to_unicode(src), _path_to_unicode(dst),
87
+ _windows_default_flags
88
+ ))
89
+
90
+
91
+ def replace_atomic(src, dst):
92
+ '''
93
+ Move ``src`` to ``dst``. If ``dst`` exists, it will be silently
94
+ overwritten.
95
+
96
+ Both paths must reside on the same filesystem for the operation to be
97
+ atomic.
98
+ '''
99
+ return _replace_atomic(src, dst)
100
+
101
+
102
+ def move_atomic(src, dst):
103
+ '''
104
+ Move ``src`` to ``dst``. There might a timewindow where both filesystem
105
+ entries exist. If ``dst`` already exists, :py:exc:`FileExistsError` will be
106
+ raised.
107
+
108
+ Both paths must reside on the same filesystem for the operation to be
109
+ atomic.
110
+ '''
111
+ return _move_atomic(src, dst)
112
+
113
+
114
+ class AtomicWriter(object):
115
+ '''
116
+ A helper class for performing atomic writes. Usage::
117
+
118
+ with AtomicWriter(path).open() as f:
119
+ f.write(...)
120
+
121
+ :param path: The destination filepath. May or may not exist.
122
+ :param mode: The filemode for the temporary file. This defaults to `wb` in
123
+ Python 2 and `w` in Python 3.
124
+ :param overwrite: If set to false, an error is raised if ``path`` exists.
125
+ Errors are only raised after the file has been written to. Either way,
126
+ the operation is atomic.
127
+ :param open_kwargs: Keyword-arguments to pass to the underlying
128
+ :py:func:`open` call. This can be used to set the encoding when opening
129
+ files in text-mode.
130
+
131
+ If you need further control over the exact behavior, you are encouraged to
132
+ subclass.
133
+ '''
134
+
135
+ def __init__(self, path, mode=DEFAULT_MODE, overwrite=False,
136
+ **open_kwargs):
137
+ if 'a' in mode:
138
+ raise ValueError(
139
+ 'Appending to an existing file is not supported, because that '
140
+ 'would involve an expensive `copy`-operation to a temporary '
141
+ 'file. Open the file in normal `w`-mode and copy explicitly '
142
+ 'if that\'s what you\'re after.'
143
+ )
144
+ if 'x' in mode:
145
+ raise ValueError('Use the `overwrite`-parameter instead.')
146
+ if 'w' not in mode:
147
+ raise ValueError('AtomicWriters can only be written to.')
148
+
149
+ # Attempt to convert `path` to `str` or `bytes`
150
+ if fspath is not None:
151
+ path = fspath(path)
152
+
153
+ self._path = path
154
+ self._mode = mode
155
+ self._overwrite = overwrite
156
+ self._open_kwargs = open_kwargs
157
+
158
+ def open(self):
159
+ '''
160
+ Open the temporary file.
161
+ '''
162
+ return self._open(self.get_fileobject)
163
+
164
+ @contextlib.contextmanager
165
+ def _open(self, get_fileobject):
166
+ f = None # make sure f exists even if get_fileobject() fails
167
+ try:
168
+ success = False
169
+ with get_fileobject(**self._open_kwargs) as f:
170
+ yield f
171
+ self.sync(f)
172
+ self.commit(f)
173
+ success = True
174
+ finally:
175
+ if not success:
176
+ try:
177
+ self.rollback(f)
178
+ except Exception:
179
+ pass
180
+
181
+ def get_fileobject(self, suffix="", prefix=tempfile.gettempprefix(),
182
+ dir=None, **kwargs):
183
+ '''Return the temporary file to use.'''
184
+ if dir is None:
185
+ dir = os.path.normpath(os.path.dirname(self._path))
186
+ descriptor, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
187
+ dir=dir)
188
+ # io.open() will take either the descriptor or the name, but we need
189
+ # the name later for commit()/replace_atomic() and couldn't find a way
190
+ # to get the filename from the descriptor.
191
+ os.close(descriptor)
192
+ kwargs['mode'] = self._mode
193
+ kwargs['file'] = name
194
+ return io.open(**kwargs)
195
+
196
+ def sync(self, f):
197
+ '''responsible for clearing as many file caches as possible before
198
+ commit'''
199
+ f.flush()
200
+ _proper_fsync(f.fileno())
201
+
202
+ def commit(self, f):
203
+ '''Move the temporary file to the target location.'''
204
+ if self._overwrite:
205
+ replace_atomic(f.name, self._path)
206
+ else:
207
+ move_atomic(f.name, self._path)
208
+
209
+ def rollback(self, f):
210
+ '''Clean up all temporary resources.'''
211
+ os.unlink(f.name)
212
+
213
+
214
+ def atomic_write(path, writer_cls=AtomicWriter, **cls_kwargs):
215
+ '''
216
+ Simple atomic writes. This wraps :py:class:`AtomicWriter`::
217
+
218
+ with atomic_write(path) as f:
219
+ f.write(...)
220
+
221
+ :param path: The target path to write to.
222
+ :param writer_cls: The writer class to use. This parameter is useful if you
223
+ subclassed :py:class:`AtomicWriter` to change some behavior and want to
224
+ use that new subclass.
225
+
226
+ Additional keyword arguments are passed to the writer class. See
227
+ :py:class:`AtomicWriter`.
228
+ '''
229
+ return writer_cls(path, **cls_kwargs).open()
@@ -0,0 +1 @@
1
+ from atomicwrites import *
@@ -1 +1,2 @@
1
1
  six==1.16.0
2
+ atomicwrites==1.4.1
@@ -74,7 +74,7 @@ class MayaVirusCollector(object):
74
74
  @property
75
75
  def maya_install_root(self):
76
76
  """Return the Maya installation root directory."""
77
- return os.environ["MAYA_LOCATION"]
77
+ return os.environ.get("MAYA_LOCATION", "")
78
78
 
79
79
  @property
80
80
  def user_script_path(self):
@@ -1,29 +1,35 @@
1
1
  # Import built-in modules
2
- from contextlib import contextmanager
3
2
  import glob
4
3
  import importlib
5
4
  import json
6
5
  import logging
7
6
  import os
8
- import random
9
7
  import re
10
8
  import shutil
11
- import string
12
9
  import tempfile
13
10
 
14
11
  # Import local modules
15
12
  from maya_umbrella._vendor import six
13
+ from maya_umbrella._vendor.atomicwrites import atomic_write
16
14
  from maya_umbrella.constants import PACKAGE_NAME
17
15
  from maya_umbrella.signatures import FILE_VIRUS_SIGNATURES
18
16
 
19
17
 
20
18
  def this_root():
21
- """Return the absolute path of the current file's directory."""
19
+ """Return the absolute path of the current file's directory.
20
+
21
+ Returns:
22
+ str: The absolute path of the current file's directory.
23
+ """
22
24
  return os.path.abspath(os.path.dirname(__file__))
23
25
 
24
26
 
25
27
  def safe_remove_file(file_path):
26
- """Remove the file at the given path without raising an error if the file does not exist."""
28
+ """Remove the file at the given path without raising an error if the file does not exist.
29
+
30
+ Args:
31
+ file_path (str): Path to the file to remove.
32
+ """
27
33
  try:
28
34
  os.remove(file_path)
29
35
  except (OSError, IOError): # noqa: UP024
@@ -31,7 +37,11 @@ def safe_remove_file(file_path):
31
37
 
32
38
 
33
39
  def safe_rmtree(path):
34
- """Remove the directory at the given path without raising an error if the directory does not exist."""
40
+ """Remove the directory at the given path without raising an error if the directory does not exist.
41
+
42
+ Args:
43
+ path (str): Path to the directory to remove.
44
+ """
35
45
  try:
36
46
  shutil.rmtree(path)
37
47
  except (OSError, IOError): # noqa: UP024
@@ -54,7 +64,14 @@ def read_file(path):
54
64
 
55
65
 
56
66
  def read_json(path):
57
- """Read the content of the file at the given path."""
67
+ """Read the content of a JSON file at the given path.
68
+
69
+ Args:
70
+ path (str): Path to the JSON file.
71
+
72
+ Returns:
73
+ dict: The content of the JSON file as a dictionary, or an empty dictionary if the file could not be read.
74
+ """
58
75
  options = {"encoding": "utf-8"} if six.PY3 else {}
59
76
  with open(path, **options) as file_:
60
77
  try:
@@ -65,53 +82,28 @@ def read_json(path):
65
82
 
66
83
 
67
84
  def write_file(path, content):
68
- """Write the given content to the file at the given path."""
69
- with atomic_writes(path, "wb") as file_:
70
- file_.write(six.ensure_binary(content))
71
-
72
-
73
- @contextmanager
74
- def atomic_writes(src, mode):
75
- """Context manager for atomic writes to a file.
76
-
77
- This context manager ensures that the file is only written to disk if the write operation completes without errors.
85
+ """Write the given content to the file at the given path.
78
86
 
79
87
  Args:
80
- src (str): Path to the file to be written.
81
- mode (str): Mode in which the file is opened, like 'r', 'w', 'a', etc.
82
- **options: Arbitrary keyword arguments that are passed to the built-in open() function.
83
-
84
- Yields:
85
- file object: The opened file object.
86
-
87
- Raises:
88
- AttributeError: If the os module does not have the 'replace' function (Python 2 compatibility).
88
+ path (str): Path to the file to write.
89
+ content (str): Content to write to the file.
89
90
  """
90
- temp_path = os.path.join(os.path.dirname(src), "._{}".format(id_generator()))
91
- with open(temp_path, mode) as f:
92
- yield f
93
- try:
94
- os.replace(temp_path, src)
95
- except AttributeError:
96
- shutil.move(temp_path, src)
97
-
98
- def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
99
- """Generate a random string of the given size using the given characters."""
100
- return "".join(random.choice(chars) for _ in range(size))
91
+ root = os.path.dirname(path)
92
+ if not os.path.exists(root):
93
+ os.makedirs(root)
94
+ with atomic_write(path, mode="wb", overwrite=True) as file_:
95
+ file_.write(six.ensure_binary(content))
101
96
 
102
97
 
103
- def rename(src):
104
- """Rename the file at the given path to a random name and return the new path."""
105
- dst = os.path.join(os.path.dirname(src), "._{}".format(id_generator()))
106
- try:
107
- os.rename(src, dst)
108
- except (OSError, IOError): # noqa: UP024
109
- return src
110
- return dst
98
+ def load_hook(hook_file):
99
+ """Load the Python module from the given hook file.
111
100
 
101
+ Args:
102
+ hook_file (str): Path to the Python file to load.
112
103
 
113
- def load_hook(hook_file):
114
- """Load the Python module from the given hook file."""
104
+ Returns:
105
+ module: The loaded Python module.
106
+ """
115
107
  hook_name = os.path.basename(hook_file).split(".py")[0]
116
108
  if hasattr(importlib, "machinery"):
117
109
  # Python 3
@@ -132,7 +124,11 @@ def load_hook(hook_file):
132
124
 
133
125
 
134
126
  def get_hooks():
135
- """Return a list of paths to all hook files in the 'hooks' directory."""
127
+ """Return a list of paths to all hook files in the 'hooks' directory.
128
+
129
+ Returns:
130
+ list: A list of paths to all hook files in the 'hooks' directory.
131
+ """
136
132
  pattern = os.path.join(this_root(), "hooks", "*.py")
137
133
  return [hook for hook in glob.glob(pattern) if "__init__" not in hook]
138
134
 
@@ -187,6 +183,7 @@ def remove_virus_file_by_signature(file_path, signatures, output_file_path=None,
187
183
  fixed_data = replace_content_by_signatures(data, signatures).strip()
188
184
  if fixed_data:
189
185
  write_file(output_file_path or file_path, fixed_data)
186
+
190
187
  else:
191
188
  # Auto remove empty files.
192
189
  if auto_remove:
@@ -273,7 +270,14 @@ def get_backup_path(path, root_path=None):
273
270
 
274
271
 
275
272
  def get_maya_install_root(maya_version):
276
- """Get the Maya install root path."""
273
+ """Get the Maya install root path for the specified version.
274
+
275
+ Args:
276
+ maya_version (str): The version of Maya to find the install root for.
277
+
278
+ Returns:
279
+ str: The Maya install root path, or None if not found.
280
+ """
277
281
  logger = logging.getLogger(__name__)
278
282
  maya_location = os.getenv("MAYA_LOCATION")
279
283
  try:
@@ -71,7 +71,7 @@ class Vaccine(AbstractVaccine):
71
71
  if virus in script_job:
72
72
  self.api.add_infected_script_job(script_job)
73
73
 
74
- def fix_bad_hik_files(self):
74
+ def collect_infected_hik_files(self):
75
75
  """Fix all bad HIK files related to the virus."""
76
76
  pattern = os.path.join(self.api.maya_install_root, "resources/l10n/*/plug-ins/mayaHIK.pres.mel")
77
77
  for hik_mel in glob.glob(pattern):
@@ -83,8 +83,8 @@ class Vaccine(AbstractVaccine):
83
83
  """Collect all issues related to the virus."""
84
84
  self.api.add_malicious_file(os.path.join(os.getenv("APPDATA"), "syssst"))
85
85
  self.collect_infected_mel_files()
86
+ self.collect_infected_hik_files()
86
87
  self.collect_infected_nodes()
87
88
  # This only works for Maya Gui model.
88
89
  if not is_maya_standalone():
89
90
  self.collect_script_jobs()
90
- self.api.add_additionally_fix_function(self.fix_bad_hik_files)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: maya_umbrella
3
- Version: 0.12.1
3
+ Version: 0.13.0
4
4
  Summary: Check and fix maya virus.
5
5
  Home-page: https://github.com/loonghao/maya_umbrella
6
6
  License: MIT
@@ -1,17 +1,20 @@
1
1
  maya_umbrella/__init__.py,sha256=rcCnFWmELeJsGoKvLHyzC_GmZu-eT1QXjQCHRGj6HuQ,529
2
- maya_umbrella/__version__.py,sha256=PAuBI8I6F9Yu_86XjI2yaWn8QmCd9ZvK7tkZLWvEg-Q,23
2
+ maya_umbrella/__version__.py,sha256=DgpLNbv0e1LIEOOe54Db8_390i9pelMEFEnsBsNmyhA,23
3
3
  maya_umbrella/_vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ maya_umbrella/_vendor/atomicwrites/LICENSE,sha256=h4Mp8L2HitAVEpzovagvSB6G7C6Agx6QnA1nFx2SLnM,1069
5
+ maya_umbrella/_vendor/atomicwrites/__init__.py,sha256=myvxvKRBb7vebPTSUiAopsRrvsm6VojiAvET1xohT-4,6970
6
+ maya_umbrella/_vendor/atomicwrites.pyi,sha256=kofmo9N_ix4812ioT1GEwo8HTtOWvnmarf_FIKY2czg,27
4
7
  maya_umbrella/_vendor/six/__init__.pyi,sha256=tU6T1qIa_1HEM5lYdk7qw9E3GdDokvpzy5D06PhlMfA,18
5
8
  maya_umbrella/_vendor/six/moves/__init__.pyi,sha256=jVS195D1cetBSWcwZ61-NYkaaI-EKlXjoF1o0I_pG9I,24
6
9
  maya_umbrella/_vendor/six/moves/configparser.pyi,sha256=OOloYod0yM3pPe0YlqnFSkiWejxtFwN-Gs0YPYHY1Nc,37
7
10
  maya_umbrella/_vendor/six.LICENSE,sha256=i7hQxWWqOJ_cFvOkaWWtI9gq3_YPI5P8J2K2MYXo5sk,1066
8
11
  maya_umbrella/_vendor/six.py,sha256=TOOfQi7nFGfMrIvtdr6wX4wyHH8M7aknmuLfo2cBBrM,34549
9
- maya_umbrella/_vendor/vendor.txt,sha256=DaF4-6IabevawSU5v0lD3Cyskkkzge3-659bvqihS34,12
12
+ maya_umbrella/_vendor/vendor.txt,sha256=9yyM8r0ibzfkXqgcAehZId0IsvELLV18YTxi6cwF7KA,32
10
13
  maya_umbrella/cleaner.py,sha256=Xj8BU0gohvh3E7RcDwQw_xryCzN5RBYGH9UTPVxiMPA,5045
11
- maya_umbrella/collector.py,sha256=rAFmvY8Isdle89ezn2-H36hSJd77iBvPBLRPzruCycA,13111
14
+ maya_umbrella/collector.py,sha256=SiC-wpDjer7w6ofsyWTFJmUF8pPz233xDXeANNz-LrY,13119
12
15
  maya_umbrella/constants.py,sha256=SuD8OP8e0Kh3a9ohyS5_MXjo5pHNQ8MWEPtJ6puZfIU,130
13
16
  maya_umbrella/defender.py,sha256=eT4uK23uOB1V8Y3uiaU1C2Tp-s1SngrGo3TWDbSIVJY,6008
14
- maya_umbrella/filesystem.py,sha256=B9uhaRnzsj7UFwD9egDWtgR0lgBSV_iPvS0DuqcW-Vg,9537
17
+ maya_umbrella/filesystem.py,sha256=E1bs4WguYI9cKr6JnfyERu2fVE9Ii9qYUv-DcfVZa1k,9194
15
18
  maya_umbrella/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
19
  maya_umbrella/hooks/delete_turtle.py,sha256=OPFRFH1iwonHvETndrP87MZQlJLhxpe564AJE3KyJY8,761
17
20
  maya_umbrella/hooks/delete_unknown_plugin_node.py,sha256=5YXaOem-t9Em1sr3wmBqWk5He1Lm8CsOMQsSQ3ixLfs,1293
@@ -29,8 +32,8 @@ maya_umbrella/vaccine.py,sha256=aBW6pdT4tD4OMBPZ-d3E4_n16Rylz-2gb7JWzMZVPK0,1022
29
32
  maya_umbrella/vaccines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
33
  maya_umbrella/vaccines/vaccine1.py,sha256=WLo1uJElTLSjVCf5CBtRNU4HKs63my5mkHiGqTfnNEE,489
31
34
  maya_umbrella/vaccines/vaccine2.py,sha256=qYiI_-BSojgN7j4esYCGBDLeyBSneDOGUwZhKHccxh8,2170
32
- maya_umbrella/vaccines/vaccine3.py,sha256=AgYxpflStURawobBQX0F0N_4uDwyV1IWCvZ6QLMmKpA,3633
33
- maya_umbrella-0.12.1.dist-info/LICENSE,sha256=tJf0Pz8q_65AjEkm3872K1cl4jGil28vJO5Ko_LhUqc,1060
34
- maya_umbrella-0.12.1.dist-info/METADATA,sha256=u17mEb0q-lAj0D0V3GjyUhb9PjCvghyLnHWf3nQU9wM,11722
35
- maya_umbrella-0.12.1.dist-info/WHEEL,sha256=IrRNNNJ-uuL1ggO5qMvT1GGhQVdQU54d6ZpYqEZfEWo,92
36
- maya_umbrella-0.12.1.dist-info/RECORD,,
35
+ maya_umbrella/vaccines/vaccine3.py,sha256=f1jO9pyyZ_Ep18HR-fCQfGEi-TnNKXzjdCi9ORSZUx0,3613
36
+ maya_umbrella-0.13.0.dist-info/LICENSE,sha256=tJf0Pz8q_65AjEkm3872K1cl4jGil28vJO5Ko_LhUqc,1060
37
+ maya_umbrella-0.13.0.dist-info/METADATA,sha256=3COg7DKGNx241YmlU6aVJOssLpRmpsKeLXSFa-I2Sx0,11722
38
+ maya_umbrella-0.13.0.dist-info/WHEEL,sha256=IrRNNNJ-uuL1ggO5qMvT1GGhQVdQU54d6ZpYqEZfEWo,92
39
+ maya_umbrella-0.13.0.dist-info/RECORD,,