Package: unoccupied
UnoccupiedFind an unoccupied name frombasename&occupied names.If you tired to write a bunch of code to dealunnamed note 02,pic-03.jpg,archive.part4.zipnaming problem. It's for you.FeaturesBased on basename: find an unoccupied name as close as user wanted, not randomly.Isolated: pure function API, no related outer environment like filesystem or DB.Stable: consider edge situations and already be tested.Flexible: library user can choice / overwrite naming algorithm easily.UsageBasic UsageUsebasenameandoccupiedcontainer (an iterable) to find a unoccupied name:fromunoccupiedimportunoccupiedbasename='foo'unoccupied(basename,[])# 'foo' not be occupied# >>> 'foo'unoccupied(basename,['foo'])# 'foo' already be occupied# >>> 'foo-1'unoccupied(basename,['foo','foo-1'])# 'foo' & 'foo-1' already be occupied# >>> 'foo-2'Name FinderName finder offer an algorithm to find (or generate) an unoccupied name.Let's try to change the default name finder:fromunoccupiedimportunoccupiedfromunoccupiedimportNumberNameFinder# A built-in name finder generator# test databasename='foo'occupied=['foo','bar','foo-1']unoccupied(basename,occupied)# use defualt name finder# >>> 'foo-2'name_finder=NumberNameFinder(template='{basename}-{num:02}')# <- look hereunoccupied(basename,occupied,name_finder)# >>> 'foo-01'Another case: Assume we need to find an unoccupied filename, but, we don't want the base filenamefoo.txtbecomefoo.txt-1. Thefoo-1.txtis much suitable name. Try the built-inFileNameFinder().fromunoccupiedimportunoccupiedfromunoccupiedimportFileNameFinder# here# test databasename='pic.jpg'occupied=['pic.jpg','pic-1.jpg','foo']# may use os.listdir() in real caseunoccupied(basename,occupied,FileNameFinder())# <- look here# >>> 'pic-2.jpg'name_finder=FileNameFinder(template='{base}.{num:02}',start=0)unoccupied(basename,occupied,name_finder)# >>> 'pic.00.jpg'Build a Name FinderAname_finderis just a callable accept 2 arguments: (basename,norm_occupied), so feel free to build your own. e.g.:importstringfromunoccupiedimportunoccupieddefalphabet_name_finder(basename,norm_occupied):forcharinstring.ascii_lowercase:testing_name='{}-{}'.format(basename,char)iftesting_namenotinnorm_occupied:returntesting_nameunoccupied('foo',['foo'],alphabet_name_finder)# >>> 'foo-a'Or, usingBaseNameFinderclass to build aname_finder:importstringfromunoccupiedimportunoccupiedfromunoccupiedimportBaseNameFinderclassAlphabetNameFinder(BaseNameFinder):"""Basic alphabet name finder."""defids_generator(self):returnstring.ascii_lowercasealphabet_name_finder=AlphabetNameFinder()unoccupied('foo',['foo'],alphabet_name_finder)# >>> 'foo-a'classAlphabetNameFinder2(BaseNameFinder):"""Configurable alphabet name finder."""def__init__(self,template):# here is a configurable option.self.template=templatedefids_generator(self):returnstring.ascii_lowercasedefformatter(self,basename,id):# change formatting algorithmreturnself.template.format(basename=basename,id=id)alphabet_name_finder2=AlphabetNameFinder2(template='{basename}.{id}')unoccupied('foo',['foo'],alphabet_name_finder2)# >>> 'foo.a'As you see.BaseNameFinderpacked some tedious work like for-loop & infinite loop checking. And good for offer some configurable options for further usage.Referencefunction unoccupied(basename, occupied, name_finder=NumberNameFinder(), skipbase=False)Find a unoccupied name.basename: (str) the wanted basename.occupied: (str of iterable) the names already be occupied.name_finderis a callable with 2 arguments (basename,norm_occupied). This function only be called whenbasenamecannot use directly, and it should returnNoneor string. ReturnNonemean it can't find any unoccupied name and causeunoccupied()raiseUnoccupiedNameNotFoundexception.Hint:occupiedwill be convert tofrozensetdata type (we call itnorm_occupied) and inject toname_finder. If and only if you try to build aname_finderby yourself, you may need to know that.skipbaseis a boolean value. IfTrue,basenamewill not return directly, no matter thebasenamealready inoccupiedor not. So user can generate a consistent name series likepic-01,pic-02and withoutpic.class BaseNameFinder()This is the base class of built-in NameFinder class. It has 2 method which can (but not necessary) be overwrited:method ids_generator(self) -> IterableThis method will create a series ofidand push intoself.formatter().method formatter(self, basename, id) -> strThis method can assemblebasenameandidthen return a string by any algorithm.class NumberNameFinder(template='{basename}-{num}', start=1)Generate aname_finderto find an unoccupied name withbasenameand an increasing number.Thetemplateargument is a pythonstr.format()template. This template can include 2 keyword params.{basename}represent the originalbasename.{num}represent an increasing number.startargument can define what{num}starts from.class FileNameFinder(template='{base}-{num}', start=1)Generate aname_finderto find an unoccupied name with processed filename and an increasing number.Thetemplateargument is a pythonstr.format()template. This template can include 2 keyword params.{base}represent the filename without extension.{num}represent an increasing number. Hint: the filename extension will be appended automatically.startargument can define what{num}starts from.Test./setup.pytest# or pytestInstallpipinstallunoccupied