batchmp 1.4__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.
Files changed (68) hide show
  1. batchmp/__init__.py +0 -0
  2. batchmp/cli/__init__.py +0 -0
  3. batchmp/cli/base/__init__.py +0 -0
  4. batchmp/cli/base/bmp_dispatch.py +60 -0
  5. batchmp/cli/base/bmp_options.py +349 -0
  6. batchmp/cli/base/vchk.py +47 -0
  7. batchmp/cli/bmfp/__init__.py +0 -0
  8. batchmp/cli/bmfp/bmfp_dispatch.py +120 -0
  9. batchmp/cli/bmfp/bmfp_options.py +442 -0
  10. batchmp/cli/renamer/__init__.py +0 -0
  11. batchmp/cli/renamer/renamer_dispatch.py +135 -0
  12. batchmp/cli/renamer/renamer_options.py +355 -0
  13. batchmp/cli/tagger/__init__.py +0 -0
  14. batchmp/cli/tagger/tagger_dispatch.py +143 -0
  15. batchmp/cli/tagger/tagger_options.py +338 -0
  16. batchmp/commons/__init__.py +0 -0
  17. batchmp/commons/chainedhandler.py +102 -0
  18. batchmp/commons/descriptors.py +173 -0
  19. batchmp/commons/progressbar.py +154 -0
  20. batchmp/commons/taskprocessor.py +149 -0
  21. batchmp/commons/utils.py +194 -0
  22. batchmp/ffmptools/__init__.py +0 -0
  23. batchmp/ffmptools/ffcommands/__init__.py +0 -0
  24. batchmp/ffmptools/ffcommands/cmdopt.py +115 -0
  25. batchmp/ffmptools/ffcommands/convert.py +130 -0
  26. batchmp/ffmptools/ffcommands/cuesplit.py +223 -0
  27. batchmp/ffmptools/ffcommands/denoise.py +173 -0
  28. batchmp/ffmptools/ffcommands/fragment.py +121 -0
  29. batchmp/ffmptools/ffcommands/normalize_peak.py +135 -0
  30. batchmp/ffmptools/ffcommands/segment.py +157 -0
  31. batchmp/ffmptools/ffcommands/silencesplit.py +159 -0
  32. batchmp/ffmptools/ffrunner.py +189 -0
  33. batchmp/ffmptools/ffutils.py +300 -0
  34. batchmp/ffmptools/processors/__init__.py +0 -0
  35. batchmp/ffmptools/processors/basefp.py +92 -0
  36. batchmp/ffmptools/processors/ffentry.py +81 -0
  37. batchmp/ffmptools/utils/__init__.py +0 -0
  38. batchmp/ffmptools/utils/cueparse.py +227 -0
  39. batchmp/ffmptools/utils/cuesheet.py +239 -0
  40. batchmp/fstools/__init__.py +0 -0
  41. batchmp/fstools/builders/__init__.py +0 -0
  42. batchmp/fstools/builders/fsb.py +221 -0
  43. batchmp/fstools/builders/fsentry.py +60 -0
  44. batchmp/fstools/builders/fsprms.py +372 -0
  45. batchmp/fstools/dirtools.py +549 -0
  46. batchmp/fstools/fsutils.py +272 -0
  47. batchmp/fstools/rename.py +390 -0
  48. batchmp/fstools/walker.py +79 -0
  49. batchmp/tags/__init__.py +0 -0
  50. batchmp/tags/handlers/__init__.py +0 -0
  51. batchmp/tags/handlers/basehandler.py +99 -0
  52. batchmp/tags/handlers/ffmphandler.py +75 -0
  53. batchmp/tags/handlers/ffmphandlers/__init__.py +0 -0
  54. batchmp/tags/handlers/ffmphandlers/base.py +243 -0
  55. batchmp/tags/handlers/mtghandler.py +56 -0
  56. batchmp/tags/handlers/pmhandler.py +36 -0
  57. batchmp/tags/handlers/tagsholder.py +264 -0
  58. batchmp/tags/output/__init__.py +0 -0
  59. batchmp/tags/output/formatters.py +218 -0
  60. batchmp/tags/processors/__init__.py +0 -0
  61. batchmp/tags/processors/basetp.py +266 -0
  62. batchmp-1.4.dist-info/METADATA +422 -0
  63. batchmp-1.4.dist-info/RECORD +68 -0
  64. batchmp-1.4.dist-info/WHEEL +5 -0
  65. batchmp-1.4.dist-info/entry_points.txt +5 -0
  66. batchmp-1.4.dist-info/licenses/LICENSE +11 -0
  67. batchmp-1.4.dist-info/top_level.txt +1 -0
  68. batchmp-1.4.dist-info/zip-safe +1 -0
@@ -0,0 +1,338 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf8
3
+ ## Copyright (c) 2014 Arseniy Kuznetsov
4
+ ##
5
+ ## This program is free software; you can redistribute it and/or
6
+ ## modify it under the terms of the GNU General Public License
7
+ ## as published by the Free Software Foundation; either version 2
8
+ ## of the License, or (at your option) any later version.
9
+ ##
10
+ ## This program is distributed in the hope that it will be useful,
11
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ## GNU General Public License for more details.
14
+
15
+
16
+ """ Batch management of media files metadata (tags & artwork)
17
+ . Supported formats:
18
+ 'MP3', 'MP4', 'M4A', M4V', 'AIFF', 'ASF', 'QuickTime / MOV',
19
+ 'FLAC', 'MonkeysAudio', 'Musepack',
20
+ 'Ogg FLAC', 'Ogg Speex', 'Ogg Theora', 'Ogg Vorbis',
21
+ 'True Audio', 'WavPack', 'OptimFROG'
22
+
23
+ 'AVI', 'FLV', 'MKV', 'MKA' (support via FFmpeg)
24
+ . source directory / source file modes
25
+ . include / exclude patterns, etc. (see list of Global Options for details)
26
+ . visualises original / targeted files metadata structure
27
+ . display sorting:
28
+ .. by size/date, ascending/descending
29
+ . action commands:
30
+ .. print Prints media files
31
+ .. set Sets tags in media files, including artwork, e.g:
32
+ $ tagger set --album 'The Album' -art '~/Desktop/art.jpg'
33
+ Supports expandable templates. To specify a template value,
34
+ use the long tag field name preceded by $:
35
+ $ tagger set --title '$title, $track of $tracktotal'
36
+ In addition to tag fields templates, file system names are also supported:
37
+ $ tagger set --title '$filename' --album '$dirname' --artist '$pardirname'...
38
+ .. copy Copies tags from a specified media file
39
+ .. index Indexes Track / Track Total tags
40
+ .. remove Removes tags from media files
41
+ .. replace RegExp-based replace in specified tags
42
+ e.g., to remove the first three characters in title:
43
+ $ tagger replace -tf 'title' -fs '^[\s\S]{0,3}' -rs ''
44
+ .. capitalize Capitalizes words in specified tags
45
+ .. detauch Extracts artwork
46
+
47
+ Usage: tagger [-h] [-d DIR] [-f FILE] [GLobal Options] {Commands}[Commands Options]
48
+ Input source mode:
49
+ [-d, --dir] Source directory (default is the current directory)
50
+ [-f, --file] File to process
51
+
52
+ Recursion mode:
53
+ [-r, --recursive] Recurse into nested folders
54
+ [-el, --end-level] End level for recursion into nested folders
55
+
56
+ Filter files or folders:
57
+ [-in, --include] Include: Unix-style name patterns separated by ';'
58
+ [-sh, --show-hidden] Shows hidden files
59
+ [-ex, --exclude] Exclude: Unix-style name patterns separated by ';'
60
+ (excludes hidden files by default)
61
+ [-fd, --filter-dirs] Enable Include/Exclude patterns on directories
62
+ [-af, --all-files] Disable Include/Exclude patterns on files
63
+ (shows hidden files excluded by default)
64
+
65
+ Miscellaneous:
66
+ [-s, --sort]{na|nd|sa|sd} Sort order for files / folders (name | date, asc | desc)
67
+ [-ni, nested-indent] Indent for printing nested directories
68
+ [-q, --quiet] Do not visualise changes / show messages during processing
69
+
70
+ Commands:
71
+ {print, set, copy, index, remove, replace, capitalize, detauch, version, info}
72
+ $ tagger {command} -h #run this for detailed help on individual commands
73
+ """
74
+
75
+ from batchmp.cli.base.bmp_options import BatchMPArgParser, BatchMPHelpFormatter, BatchMPBaseCommands
76
+ from batchmp.tags.handlers.tagsholder import TagHolder
77
+ from batchmp.fstools.fsutils import FSH
78
+ from batchmp.fstools.builders.fsentry import FSEntryDefaults
79
+
80
+ class TaggerCommands(BatchMPBaseCommands):
81
+ SET = 'set'
82
+ COPY = 'copy'
83
+ INDEX = 'index'
84
+ REMOVE = 'remove'
85
+ REPLACE = 'replace'
86
+ CAPITALIZE = 'capitalize'
87
+ DETAUCH = 'detauch'
88
+
89
+ @classmethod
90
+ def commands_meta(cls):
91
+ return ''.join(('{',
92
+ '{}, '.format(cls.PRINT),
93
+ '{}, '.format(cls.SET),
94
+ '{}, '.format(cls.COPY),
95
+ '{}, '.format(cls.INDEX),
96
+ '{}, '.format(cls.REMOVE),
97
+ '{}, '.format(cls.REPLACE),
98
+ '{}, '.format(cls.CAPITALIZE),
99
+ '{}, '.format(cls.DETAUCH),
100
+ '{}, '.format(cls.INFO),
101
+ '{}'.format(cls.VERSION),
102
+ '}'))
103
+
104
+
105
+ class TaggerArgParser(BatchMPArgParser):
106
+ ''' Tagger Commands parsing
107
+ '''
108
+ SUPPORTED_TEXTUAL_TAGGABLE_FIELDS = [field for field in sorted(TagHolder.textual_fields())]
109
+ SUPPORTED_TAGGABLE_FIELDS = [field for field in sorted(TagHolder.taggable_fields())]
110
+
111
+ def __init__(self):
112
+ self._script_name = 'Tagger'
113
+ self._description = \
114
+ '''
115
+ Tagger manages media metadata, such as tags and
116
+ artwork. It can read and write metadata across
117
+ many different formats, with support for advanced
118
+ metadata manipulation such as regexp-based replace
119
+ in tags, expandable template processing, etc.
120
+
121
+ As default behavior, Tagger first visualises targeted
122
+ changes and ask for confirmation before actually
123
+ changing anything.
124
+ '''
125
+
126
+ # Args parsing
127
+ def parse_commands(self, parser):
128
+ ''' parses Tagger commands
129
+ '''
130
+ def _add_arg_diff_tags_only_mode(parser):
131
+ parser.add_argument('-do', '--diff-only', dest = 'diff_tags_only',
132
+ help ='Show only changed tags in the confirmation propmt',
133
+ action = 'store_true')
134
+
135
+ subparsers = parser.add_subparsers(dest='sub_cmd',
136
+ title = 'Tagger Commands',
137
+ metavar = TaggerCommands.commands_meta())
138
+ self._add_version(subparsers)
139
+ self._add_info(subparsers)
140
+
141
+ # Print
142
+ print_parser = subparsers.add_parser(TaggerCommands.PRINT,
143
+ description = 'Prints info about media files metadata, such as tags and artwork',
144
+ formatter_class = BatchMPHelpFormatter)
145
+ print_parser.add_argument('-sl', '--startlevel', dest='start_level',
146
+ help = 'Initial nested level for printing (0, i.e. root source directory by default)',
147
+ type = int,
148
+ default = 0)
149
+ print_parser.add_argument('-ss', '--showsize', dest='show_size',
150
+ help ='Shows files size',
151
+ action = 'store_true')
152
+ print_parser.add_argument('-ff', '--fullformat', dest='full_format',
153
+ help ='Shows all media tags',
154
+ action = 'store_true')
155
+ print_parser.add_argument('-st', '--showstats', dest='show_stats',
156
+ help ='Shows media file statistics',
157
+ action = 'store_true')
158
+
159
+ # Set Tags
160
+ set_tags_parser = subparsers.add_parser(TaggerCommands.SET,
161
+ description = 'Sets specified tags in media files. ' \
162
+ 'Supports expandable templates, such as $filename, $dirname, $pardirname, $title, $album, ... ',
163
+ formatter_class = BatchMPHelpFormatter)
164
+ set_tags_parser.add_argument('-ti', '--title', dest='title',
165
+ help = "Sets the Title tag",
166
+ type = str)
167
+ set_tags_parser.add_argument('-al', '--album', dest='album',
168
+ help = "Sets the Album tag",
169
+ type = str)
170
+ set_tags_parser.add_argument('-ar', '--artist', dest='artist',
171
+ help = "Sets the Artist tag",
172
+ type = str)
173
+ set_tags_parser.add_argument('-aa', '--albumartist', dest='albumartist',
174
+ help = "Sets the Album Artist tag",
175
+ type = str)
176
+ set_tags_parser.add_argument('-g', '--genre', dest='genre',
177
+ help = "Sets the Genre tag",
178
+ type = str)
179
+ set_tags_parser.add_argument('-c', '--composer', dest='composer',
180
+ help = "Sets the Composer tag",
181
+ type = str)
182
+ set_tags_parser.add_argument('-tr', '--track', dest='track',
183
+ help = "Sets the Track tag",
184
+ type = int)
185
+ set_tags_parser.add_argument('-tt', '--tracktotal', dest='tracktotal',
186
+ help = 'Set the Track Total tag for selected media files',
187
+ type = int)
188
+ set_tags_parser.add_argument('-d', '--disc', dest='disc',
189
+ help = "Sets the Disc tag",
190
+ type = int)
191
+ set_tags_parser.add_argument('-dt', '--disctotal', dest='disctotal',
192
+ help = "Sets the Disctotal tag",
193
+ type = int)
194
+ set_tags_parser.add_argument('-y', '--year', dest='year',
195
+ help = "Sets the Year tag",
196
+ type = int)
197
+ set_tags_parser.add_argument('-en', '--encoder', dest='encoder',
198
+ help = "Sets the Encoder tag",
199
+ type = str)
200
+ set_tags_parser.add_argument('-art', '--artwork', dest='artwork',
201
+ help = "Sets Artwork Image from file path or URL",
202
+ type = lambda fpath: self._is_valid_url_or_file_path(parser, fpath))
203
+ set_tags_parser.add_argument('-bm', '--bpm', dest='bpm',
204
+ help = "Sets the BPM tag",
205
+ type = str)
206
+ set_tags_parser.add_argument('-cmp', '--compilaton', dest='compilaton',
207
+ help = "Sets the Compilaton tag",
208
+ type = lambda fpath: self._is_boolean(parser, fpath))
209
+ set_tags_parser.add_argument('-grp', '--grouping', dest='grouping',
210
+ help = "Sets the Grouping tag",
211
+ type = str)
212
+ set_tags_parser.add_argument('-com', '--comments', dest='comments',
213
+ help = "Sets the Comments tag",
214
+ type = str)
215
+ set_tags_parser.add_argument('-lr', '--lyrics', dest='lyrics',
216
+ help = "Sets the Lyrics tag",
217
+ type = str)
218
+ self._add_arg_display_curent_state_mode(set_tags_parser)
219
+ _add_arg_diff_tags_only_mode(set_tags_parser)
220
+
221
+ # Copy Tags
222
+ copy_tags_parser = subparsers.add_parser(TaggerCommands.COPY,
223
+ description = 'Copies tags from a specified media file',
224
+ formatter_class = BatchMPHelpFormatter)
225
+ copy_tags_parser.add_argument('-th', '--tagholder', dest='tagholder',
226
+ help = "TagHolder Media file: /Path_to_TagHolder_Media_File",
227
+ required = True,
228
+ type = lambda fpath: self._is_valid_file_path(parser, fpath))
229
+ self._add_arg_display_curent_state_mode(copy_tags_parser)
230
+ _add_arg_diff_tags_only_mode(copy_tags_parser)
231
+
232
+ # Index
233
+ index_parser = subparsers.add_parser(TaggerCommands.INDEX,
234
+ description = 'Index Tracks for selected media files',
235
+ formatter_class = BatchMPHelpFormatter)
236
+ index_parser.add_argument('-sf', '--startfrom', dest='start_from',
237
+ help = 'A number from which the indexing starts, 1 by default',
238
+ type = int,
239
+ default = 1)
240
+ self._add_arg_display_curent_state_mode(index_parser)
241
+ _add_arg_diff_tags_only_mode(index_parser)
242
+
243
+ # Remove Tags
244
+ remove_tags_parser = subparsers.add_parser(TaggerCommands.REMOVE,
245
+ description = 'Remove tags from media files',
246
+ formatter_class = BatchMPHelpFormatter)
247
+ remove_tags_parser.add_argument('-tf', '--tag-fields', dest='tag_fields',
248
+ help = "Comma-separated list of tag fields to remove. " \
249
+ "Supported tag fields: {}".format(', '.join(self.SUPPORTED_TAGGABLE_FIELDS)),
250
+ type = str)
251
+ self._add_arg_display_curent_state_mode(remove_tags_parser)
252
+ _add_arg_diff_tags_only_mode(remove_tags_parser)
253
+
254
+ # Replace Tags
255
+ replace_parser = subparsers.add_parser(TaggerCommands.REPLACE,
256
+ description = 'RegExp-based replace in specified tag fields',
257
+ formatter_class = BatchMPHelpFormatter)
258
+ replace_parser.add_argument('-tf', '--tag-fields', dest='tag_fields',
259
+ help = "Comma-separated list of tag fields in which to replace. " \
260
+ "Supported tag fields: {}".format(', '.join(self.SUPPORTED_TEXTUAL_TAGGABLE_FIELDS)),
261
+ type = str,
262
+ required=True)
263
+ replace_parser.add_argument('-fs', '--find-string', dest='find_str',
264
+ help = "Find pattern to look for",
265
+ type = str,
266
+ required=True)
267
+ replace_parser.add_argument('-rs', '--replace-string', dest='replace_str',
268
+ help = "Replace pattern to replace with."\
269
+ "If not specified and there is a match from the find pattern," \
270
+ "the entire string will be replaced with that match",
271
+ type = str)
272
+ replace_parser.add_argument('-ic', '--ignorecase', dest='ignore_case',
273
+ help = 'Case insensitive',
274
+ action = 'store_true')
275
+ self._add_arg_display_curent_state_mode(replace_parser)
276
+ _add_arg_diff_tags_only_mode(replace_parser)
277
+
278
+ # Capitalize Tags
279
+ capitalize_parser = subparsers.add_parser(TaggerCommands.CAPITALIZE,
280
+ description = 'Capitalize words in specified tag fields',
281
+ formatter_class = BatchMPHelpFormatter)
282
+ capitalize_parser.add_argument('-tf', '--tag-fields', dest='tag_fields',
283
+ help = "Comma-separated list of tag fields in which to capitalize words. " \
284
+ "Supported tag fields: {}".format(', '.join(self.SUPPORTED_TEXTUAL_TAGGABLE_FIELDS)),
285
+ type = str,
286
+ required=True)
287
+ self._add_arg_display_curent_state_mode(capitalize_parser)
288
+ _add_arg_diff_tags_only_mode(capitalize_parser)
289
+
290
+ # Detauch Art
291
+ detauch_parser = subparsers.add_parser(TaggerCommands.DETAUCH,
292
+ description = 'Detauches art into specified target directory',
293
+ formatter_class = BatchMPHelpFormatter)
294
+ detauch_parser.add_argument("-td", "--target_dir", dest = "target_dir",
295
+ type = lambda fpath: FSH.full_path(fpath),
296
+ default = None,
297
+ help = "Target directory for detauching art. When omitted, detauched art will be stored in "
298
+ "the top-level media files source directory")
299
+
300
+ # Args Checking
301
+ def default_command(self, args, parser):
302
+ args['sub_cmd'] = TaggerCommands.PRINT
303
+ args['start_level'] = 0
304
+ args['show_size'] = False
305
+ args['show_stats'] = False
306
+ args['full_format'] = False
307
+
308
+ def check_args(self, args, parser):
309
+ super().check_args(args, parser)
310
+
311
+ def parse_tag_fields(fields, supported_fields):
312
+ fields = [r.strip() for r in fields.split(',')]
313
+ for tag_field in fields:
314
+ if tag_field not in supported_fields:
315
+ parser.error('The tag field "{0}" is not supported\n\t' \
316
+ 'Supported tag fields: {1}'.format(tag_field, ', '.join(supported_fields)))
317
+ return fields
318
+
319
+ # only consider playable media files by default
320
+ if args['file_type'] == FSEntryDefaults.DEFAULT_FILE_TYPE:
321
+ args['file_type'] = FSEntryDefaults.DEFAULT_MEDIA_TYPE
322
+
323
+ if args['sub_cmd'] == TaggerCommands.INDEX:
324
+ if args['start_from'] < 1:
325
+ parser.error('Track indexing should start from 1, or a larger int number')
326
+
327
+ elif args['sub_cmd'] == TaggerCommands.REMOVE:
328
+ if args['tag_fields'] is not None:
329
+ args['tag_fields'] = parse_tag_fields(args['tag_fields'], \
330
+ self.SUPPORTED_TAGGABLE_FIELDS)
331
+
332
+ elif args['sub_cmd'] in (TaggerCommands.REPLACE, TaggerCommands.CAPITALIZE):
333
+ args['tag_fields'] = parse_tag_fields(args['tag_fields'], \
334
+ self.SUPPORTED_TEXTUAL_TAGGABLE_FIELDS)
335
+
336
+ elif args['sub_cmd'] == TaggerCommands.DETAUCH:
337
+ if args['target_dir'] is None:
338
+ args['target_dir'] = args['dir']
File without changes
@@ -0,0 +1,102 @@
1
+ # coding=utf8
2
+ ## Copyright (c) 2014 Arseniy Kuznetsov
3
+ ##
4
+ ## This program is free software; you can redistribute it and/or
5
+ ## modify it under the terms of the GNU General Public License
6
+ ## as published by the Free Software Foundation; either version 2
7
+ ## of the License, or (at your option) any later version.
8
+ ##
9
+ ## This program is distributed in the hope that it will be useful,
10
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ ## GNU General Public License for more details.
13
+
14
+
15
+ ''' A Chain of Responsibility impl
16
+ Usage:
17
+ >>> handler = ConcreteHandler1() + ConcreteHandler2() + ...
18
+ >>> if handler.can_handle(request):
19
+ .... handler.operation()
20
+ '''
21
+ from abc import ABCMeta, abstractmethod
22
+ from batchmp.commons.descriptors import LazyFunctionPropertyDescriptor
23
+ from weakref import ref, ReferenceType
24
+
25
+
26
+ class ChainedHandler(metaclass = ABCMeta):
27
+ class HandlersChainDispatcher:
28
+ ''' Internal dispatcher for chained handlers
29
+ '''
30
+ def __init__(self):
31
+ self._handlers_chain = []
32
+ self._responder_idx = -1
33
+
34
+ def add_handler(self, handler):
35
+ ''' Adds a handler to the chain
36
+ '''
37
+ if len(self._handlers_chain) == 0:
38
+ # the first handler owns the chain,
39
+ # hence it needs to be added as a weakref
40
+ self._handlers_chain.append(ref(handler))
41
+ else:
42
+ self._handlers_chain.append(handler)
43
+
44
+ def has_responder(self, request):
45
+ ''' Evaluates the handler chain and select a suitable responder
46
+ '''
47
+ for idx, handler in enumerate(self._handlers_chain):
48
+ if isinstance(handler, ReferenceType):
49
+ handler = handler()
50
+ if handler and handler._can_handle(request):
51
+ self._responder_idx = idx
52
+ return True
53
+ return False
54
+
55
+ @property
56
+ def responder(self):
57
+ ''' Returns the curent responder
58
+ '''
59
+ if self._responder_idx >= 0:
60
+ handler = self._handlers_chain[self._responder_idx]
61
+ if isinstance(handler, ReferenceType):
62
+ handler = handler()
63
+ return handler
64
+ else:
65
+ return None
66
+
67
+ @LazyFunctionPropertyDescriptor
68
+ def _handler_chain(self):
69
+ ''' lazily creates the chain dispatcher and
70
+ stores is as an internal property
71
+ via @LazyFunctionPropertyDescriptor
72
+ '''
73
+ handler_chain = ChainedHandler.HandlersChainDispatcher()
74
+ handler_chain.add_handler(self)
75
+ return handler_chain
76
+
77
+ @property
78
+ def responder(self):
79
+ ''' Returns active responder
80
+ '''
81
+ return self._handler_chain.responder
82
+
83
+ def __add__(self, handler):
84
+ ''' Adds a handler to the handlers chain
85
+ '''
86
+ if not isinstance(handler, ChainedHandler):
87
+ raise TypeError('ChainedHandler.__add__() expects a ChainedHandler instance')
88
+ handler._handler_chain = self._handler_chain
89
+ self._handler_chain.add_handler(handler)
90
+
91
+ return self
92
+
93
+ def can_handle(self, request):
94
+ return self._handler_chain.has_responder(request)
95
+
96
+ # Abstract methods
97
+ @abstractmethod
98
+ def _can_handle(self, request):
99
+ ''' implement in specific handlers
100
+ '''
101
+ return False
102
+
@@ -0,0 +1,173 @@
1
+ # coding=utf8
2
+ ## Copyright (c) 2014 Arseniy Kuznetsov
3
+ ##
4
+ ## This program is free software; you can redistribute it and/or
5
+ ## modify it under the terms of the GNU General Public License
6
+ ## as published by the Free Software Foundation; either version 2
7
+ ## of the License, or (at your option) any later version.
8
+ ##
9
+ ## This program is distributed in the hope that it will be useful,
10
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ ## GNU General Public License for more details.
13
+
14
+
15
+ ''' Properties Descriptors Types
16
+ '''
17
+ from importlib import import_module
18
+ import inspect
19
+ from types import MethodType, FunctionType
20
+ from weakref import WeakKeyDictionary, WeakMethod
21
+
22
+
23
+ class PropertyDescriptor:
24
+ ''' Base Property Descriptor (Python 3.6+)
25
+ '''
26
+ # the python 3.6 initializer:
27
+ def __set_name__(self, owner, name):
28
+ self.name = name
29
+
30
+ def __get__(self, instance, owner):
31
+ if instance is None:
32
+ return self
33
+ return instance.__dict__.get(self.name, None)
34
+
35
+ def __set__(self, instance, value):
36
+ instance.__dict__[self.name] = value
37
+
38
+
39
+ class LazyClassPropertyDescriptor(PropertyDescriptor):
40
+ ''' Dynamically loads class property of a given custom type
41
+ Example:
42
+ fs_entry_builder = LazyClassPropertyDescriptor('batchmp.fstools.builders.fsb.FSEntryBuilderBase')
43
+ '''
44
+ def __init__(self, property_type_classpath, initialize = True):
45
+ super().__init__()
46
+ self._pt_cpath = property_type_classpath
47
+
48
+ def __get__(self, instance, owner=None):
49
+ value = super().__get__(instance, owner = owner)
50
+ if not value:
51
+ value = self.load_lazy_property_class()
52
+ self.__set__(instance, value)
53
+ return value
54
+
55
+ def __set__(self, instance, value):
56
+ classname = value.__name__ if inspect.isclass(value) else value.__class__.__name__
57
+ classpath = '.'.join((value.__module__, classname))
58
+ if classpath == self._pt_cpath:
59
+ super().__set__(instance, value)
60
+ else:
61
+ raise TypeError("Type error: {0} is not {1}".format(classpath, self._pt_cpath))
62
+
63
+ # Helpers
64
+ def load_lazy_property_class(self):
65
+ split_path = self._pt_cpath.split('.')
66
+ module_path = '.'.join(split_path[:-1])
67
+ class_name = split_path[-1:][0]
68
+ module = import_module(module_path)
69
+ return getattr(module, class_name)
70
+
71
+
72
+ class LazyInstancePropertyDescriptor(LazyClassPropertyDescriptor):
73
+ ''' Dynamically loads instance property of a given custom type
74
+ Example:
75
+ tag_holder = LazyInstancePropertyDescriptor('batchmp.tags.handlers.tagsholder.TagHolder')
76
+ '''
77
+ # Helpers
78
+ def load_lazy_property_class(self):
79
+ return super().load_lazy_property_class()()
80
+
81
+
82
+ class LazyFunctionPropertyDescriptor:
83
+ ''' Provides lazy property access on the class level
84
+ '''
85
+ def __init__(self, func):
86
+ self._func = func
87
+ def __get__(self, instance, owner=None):
88
+ if instance is None:
89
+ return self
90
+ # this method will only be called when
91
+ # the property has not yet been set on the instance level
92
+ # so checking the instance dictionary here is mostly for the sake of good manners...
93
+ value = instance.__dict__.get(self._func.__name__)
94
+ if not value:
95
+ # the property has not been set yet
96
+ # calculate the value and store it on the instance
97
+ value = self._func(instance)
98
+ instance.__dict__[self._func.__name__] = value
99
+ return value
100
+
101
+
102
+ class FunctionPropertyDescriptor(PropertyDescriptor):
103
+ ''' A function type property descriptor
104
+ '''
105
+ def __set__(self, instance, value):
106
+ if (value is None) or isinstance(value, FunctionType):
107
+ super().__set__(instance, value)
108
+ else:
109
+ raise TypeError("Not a Function Type: {}".format(value))
110
+
111
+
112
+ class ClassPropertyDescriptor(PropertyDescriptor):
113
+ ''' A function type property descriptor
114
+ '''
115
+ def __set__(self, instance, value):
116
+ if (value is None) or inspect.isclass(value):
117
+ super().__set__(instance, value)
118
+ else:
119
+ raise TypeError("Not a Class: {}".format(instance.__class__))
120
+
121
+
122
+ class WeakMethodPropertyDescriptor(PropertyDescriptor):
123
+ ''' A bound method type property descriptor
124
+ Uses WeakMethod to prevent reference cycles
125
+ '''
126
+ def __get__(self, instance, owner=None):
127
+ value = super().__get__(instance, owner = owner)
128
+ if value:
129
+ return value()
130
+ else:
131
+ return None
132
+
133
+ def __set__(self, instance, value):
134
+ if isinstance(value, MethodType):
135
+ super().__set__(instance, WeakMethod(value))
136
+ else:
137
+ raise TypeError("Not a Method Type: {}".format(value))
138
+
139
+
140
+ class BooleanPropertyDescriptor(PropertyDescriptor):
141
+ ''' A boolean type property descriptor
142
+ '''
143
+ def __set__(self, instance, value):
144
+ if isinstance(value, bool):
145
+ super().__set__(instance, value)
146
+ else:
147
+ raise TypeError("Not a Boolean Type: {}".format(value))
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+ # class PropertyDescriptor:
156
+ # ''' Base Property Descriptor
157
+ # '''
158
+ # def __init__(self):
159
+ # self.data = WeakKeyDictionary()
160
+ #
161
+ # def __get__(self, instance, type=None):
162
+ # return self.data.get(instance)
163
+ #
164
+ # def __set__(self, instance, value):
165
+ # self.data[instance] = value
166
+ #
167
+ # def __delete__(self, instance):
168
+ # if self.data.get(instance):
169
+ # del self.data[instance]
170
+ #
171
+ # def __set_name__(self, owner, name):
172
+ # self.name = '_' + name
173
+