Package: task2a
Password generator.Module 2: Python. Task 2A.PrefaceThis project contains a solution to one of the tasks of the EPAM DevOps Initial Internal Training Course #7 in 2023.
Detailed information about the course, as well as reports on each of the completed tasks (including this one) can be foundhere.As mentioned above, the project contains a solution to task #2A as part of module #2 of learning the Python programming language.
Below you will find a detailed description of the task, as well as a brief description of the implementation.Table of ContentsTask descriptionDetailed conditionsGeneration Based on Character SetsGeneration Based on PatternsCLI interface argumentsCode descriptionImplementationStructureInstallationUsageLibraryCLIPipes and filesShowcaseGeneral ProvisionsTask descriptionThe main goal is to implement a password generator that returns whether a randomly generated password or password generated based on the passed template.Detailed conditionsWrite a utility for generating passwords according to a given template that supports the CLI interface,
be able to work in PIPE and logging (-vvv – show detailed information during processing).This password generation app should be implements two ways to generate random passwords:the random method (a password of a given length is randomly generated from a set of
characters);the pattern-based generation method is used if passwords follow special rules or fulfill certain
conditions.Generation Based on Character SetsGeneration based on a character set is very simple. You simply let Password Gen know which characters
can be used (e.g. upper-case letters, digits, ...) and Password Gen will randomly pick characters out of the
set.Defining a character set:The character set can be defined directly in the argument line. For convenience, PasswordGen offers to add
commonly used ranges of characters to the set. This is done by chouse the appropriate optional in the
argument line. Additionally, to these predefined character ranges, you can specify characters manually: all
characters that you enter in the value of the -S option will be directly added to the character set.Character sets are sets:In mathematical terms, character sets are sets, not vectors. This means that characters cannot be added
twice to the set. Either a character is in the set or it is not.For example, if you enter 'AAAAB' into the value of -S option argument line, this is exactly the same set as
'AB'. 'A' will not be 4 times as likely as 'B'! If you need to follow rules like 'character A is more likely than B',
you must use pattern-based generation + permuting password characters.Password Gen will 'optimize' your character set by removing all duplicate characters. If you'd enter the
character set 'AAAAB' into the value of -S optional argument line, the password generator, should be
optimized to the shorter character set 'AB'. Similarly, if you set the '\d' optional check box and enter '3' into
the value of the '-S' option, '3' will be ignored because it is already included in the 'Digits' character range.Supported characters:All Unicode characters in the ranges [U+0001, U+D7FF] and [U+E000, U+FFFF] except { U+0009 / '\t', U+000A
/ '\n', U+000D / '\r' } are supported. Characters in the range [U+010000, U+10FFFF] (which need to be
encoded in UTF-16 using surrogate pairs from [0xD800, 0xDFFF]) are not supported. Subsequent processing
of passwords may have further limitations (for example, the character U+FFFF is forbidden in XML files and
will be replaced or removed).Generation Based on PatternsThe password generator can create passwords using patterns. A pattern is a string defining the layout of
the new password. The following placeholders are supported:PlaceholderTypeCharacter SetdDigit0123456789lLower-Case LetterabcdefghijklmnopqrstuvwxyzLMixed-Case LetterABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzuUpper-Case LetterABCDEFGHIJKLMNOPQRSTUVWXYZpPunctuation,.;:u\Escape (Fixed Char)Use following character as is.{n}Escape (Repeat)Repeat the previous placeholder n times.[...]Custom Char SetDefine a custom character set.The \ placeholder is special: it's an escape character. The next character that follows the \ is written directly
into the generated password. If you want a \ in your password at a specific place, you must write \.Using the {n} code you can define how many times the previous placeholder should occur. The { } operator duplicates
placeholders, not generated characters. Examples:d{4} is equivalent to dddd,dL{4}a is equivalent to dLLLLa andudl{1}du is equivalent to udldu.The [...] notation can be used to define a custom character set, from which the password generator will pick one
character randomly. All characters between the '[' and ']' brackets follow the same rules as the placeholders above.
The '^' character removes the next placeholders from the character set. Examples:[dp] generates exactly 1 random character out of the set digits + punctuation,[d\m\@^\3]{5} generates 5 characters out of the set "012456789m@",[u\][u\] generates 2 characters out of the set upper-case + '_'.More examples:ddddd => generates for example: 41922, 12733, 43960, 07660, 12390, 74680, ...u{4}d{3}-l{2} => DHRF345-stu{4}[dl]{3}-l{2} => DHRF3s4-st | FHGFds4-vt | DERS774-sdGenerating Passwords that Follow RulesBelow are some examples of how the pattern generator can be used to generate passwords that follow certain rules.
Important! For all the following examples you must enable the 'Randomly permute characters of password' option (-p)!RulePatternMust consist of 2 upper-case letters, 2 lower-case letters and 2 digits.uulldddMust consist of 9 digits and 1 letter.d{9}LMust consist of 10 alphanumeric characters, where at least 1 is a letter and at least 1 is a digit.ld[Ld]{8}CLI interface can support next commands:n: Set length of password and generate random password from set {small lateral ASCII, big lateral ASCII,
digit}t: Set template for generate passwordsf: Getting list of patterns from file and generate for each random passwordc: number of passwordsvvv: Verbose mode (-v |-vv |-vvv )h: helpS: character setp: Randomly permute characters of passwordOutput:Can be support pipe redirect (output must view by formatted column or table).Code description* To better understand the gist, some segments of the actual code may be excluded or simplified in the following snippets.To describe the solution of the task in general terms, the following sequence of actions happens:transform the template into a set of placeholderstransform the set of placeholders into a set of charactersselect a random character from the list of characters repeatedly until the password reaches the desired lengthif necessary permute the characters in the password, then randomly select symbols to shuffle themdefgenerate_passwords(length:int=8,count:int=1,template:Optional[str]=None,placeholders_set:Optional[str]=None,permute:Optional[bool]=False)->List[str]:# Library entry pointdefgenerate_password_from_pattern(pattern:str)->str:# Transform pattern to the list placeholders setdefgenerate_character_set(placeholders:str)->Optional[str]:# Transform placeholders set to character setdefgenerate_password_from_character_set(character_set:str,length:int=1)->str:# Randomly choose as mach symbols as it needs bases on a password lengthThe process of transformation, whether a template or placeholder-based generation follows some simple steps.split template or placeholder-based string to a list of single placeholder elementsmultiply on n the previous element if a special placeholder like{n}is presentconvert placeholder set to a character set, using for this set type to avoid duplication of charactersexclude some character sets or a particular character from the character setpat_ls=split_pattern(n_pattern)whileany([elforelinpat_lsif<...listcontains{n}placeholder...>]):# Multiply previous element while tuples present in the listpat_ls=[dupfori,elinenumerate(pat_ls)fordupin([pat_ls[i-1]]*el[0]iftype(el)istupleandtype(pat_ls[i-1])isstrelse[el])]pat_ls=[generate_character_set(el)forelinpat_ls]# Replace placeholders to character setsIf there is a need to create a password using the designated placeholders, we execute our algorithm at a lower level.
Ultimately, the process comes down to randomly selecting characters from a list and repeating it until the desired password length is achieved.defgenerate_password_from_character_set(length:int,character_set:str)->str:password=''.join(random.choice(character_set)for_inrange(length))returnpasswordThe character set can be formed both on the basis ofCHARACTER_SETSdictionary (by default, all placeholders from this dictionary are used),
and on the basis of explicitly specified characters by using a special symbol in front of them\.CHARACTER_SETS={'d':string.digits,# Digits'l':string.ascii_lowercase,# Small lateral ASCII'L':string.ascii_letters,# Mixed-case lateral ASCII'u':string.ascii_uppercase,# Big lateral ASCII'p':',.;:',}When considering the implementation required to fulfill the conditions for the application to operate as aCLIwith a list of incoming arguments also, it can be seen that the task algorithm is wrapped by a wrapper.defmain():parser=argparse.ArgumentParser(description='Password Generator')[parser.add_argument('-'+key,**ARGUMENTS[key].to_argparse())forkey,valinARGUMENTS.items()]args=parser.parse_args()ifargs.t:generate_passwords(length=args.n,count=args.c,template=args.t,permute=args.p)elifargs.f:fori,patterninenumerate(patterns):generate_passwords(length=args.n,count=args.c,template=pattern,permute=args.p)elifargs.S:generate_passwords(length=args.n,count=args.c,placeholders_set=args.S,permute=args.p)else:generate_passwords(length=args.n,count=args.c,permute=args.p)Examining the template transformation algorithm through an exampleLet's examine the template transformation behavior with the following exampleu{2}p{5}l{2}d{2}L\-[Ld^l^\4^\5^\6^\7^\8\@\$\%\&\#\*\!]{3}.Step #1:Separate the template into individual placeholders, while keeping elements like {n}, <some character>, or [...] intact for the future list.
The result will be a list of['u', '{2}', 'p', '{5}', 'd', '{2}', 'L', '\-', '[Ld^l^\4^\5^\6^\7^\8\@\$\%\&\#\*\!]', '{3}'].t=''forelinlist(pattern):ifelin'[{\\'andnott:t=elelifelin'}]'ort=='\\':t+=elsplit_list.append(t)t=''elift:t+=elelse:split_list.append(el)Step #2:Multiply previous elements when {n} element occurs by n. The result will be a list of['u', 'u', 'p', 'p', 'p', 'p', 'p', 'd', 'd', 'L', '\-', '[Ld^l^\4^\5^\6^\7^\8\@\$\%\&\#\*\!]', '[Ld^l^\4^\5^\6^\7^\8\@\$\%\&\#\*\!]', '[Ld^l^\4^\5^\6^\7^\8\@\$\%\&\#\*\!]']Step #3:Replace all the elements with a character set usingthe placeholder set transformation algorithm.
For <some character> elements just omit the \ symbol and leave . The result will be a list of['ABCDEFGHIJKLMNOPQRSTUVWXYZ','ABCDEFGHIJKLMNOPQRSTUVWXYZ',',.;:',',.;:',',.;:',',.;:',',.;:','0123456789','0123456789','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ','-','ABCDEFGHIJKLMNOPQRSTUVWXYZ01239@$%&#*!','ABCDEFGHIJKLMNOPQRSTUVWXYZ01239@$%&#*!','ABCDEFGHIJKLMNOPQRSTUVWXYZ01239@$%&#*!']`Step #4:Randomly choose one character from a character set for each element of the list and join the list. The final result will be a string like'BA,.,:.43g-F$W'.Examining the placeholder set transformation algorithm through an exampleLet's examine the placeholder set transformation behavior with the following exampleLd^l^\4^\5^\6^\7^\8\@\$\%\&\#\*\!d.Step #1:Separate the placeholders string into individual placeholders, while keeping elements like <some character>, ^ or ^<some character> intact for the future list.
The result will be a list of['L', 'd', '^l', '^\4', '^\5', '^\6', '^\7', '^\8', '\@', '\$', '\%', '\&', '\#', '\*', '\!', 'd'].t=''forelinlist(placeholders):ifelin'^\\'andnott:t=elelift=='^'andel=='\\':t+=elelift=='^'ort=='\\'ort=='^\\':t+=elsplit_list.append(t)t=''else:split_list.append(el)Step #2:Replace placeholders with character sets and decouple list to two lists - the first one for included characters and the second one for excluded characters.
Included character list['abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ','0123456789','@','$','%','&','#','*','!','0123456789',]Excluded character list will be['abcdefghijklmnopqrstuvwxyz','4','5','6','7','8',]Step #3:Convert lists to sets of characters to eliminate the repetitions.
Included character set will be{'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9','@','$','%','&','#','*','!',}Excluded character set will be{'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','4','5','6','7','8',}Step #4:Subtract the excluded character set from the included character set to leave the differences of them and join it to the string.character_set-=excluded_character_setreturn''.join(str(c)forcincharacter_set)The differences and the final string will be{'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','9','@','$','%','&','#','*','!',}''.join(str(c)forcincharacter_set)# 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012349@$%&#*!'ImplementationPwgenis a Python package that could be added to your global or virtual environment by preferable package manager pip, pipenv, poetry, etc.
The project itself was managed and built using thePoetry library,
so if you intend to clone this repo and make some changes for your own purposes, please installPoetryor migrate to your preferred package management library.Based on the need to build and the possibility of using both the library and the CLI, the code was split into a library for importing and a script for execution
via the command line. Additionally, the package contains a showcase that demonstrates all use cases when run through the command line.To enhance the command line's functionality and expand showcase capabilities, theQuestionarylibrary is used and will be installed through a dependency link upon package installation.Structuretask_2a/
├── README.md (You are here now)
├── task_2a.toml # Poetry package management file
└── pwgen/ (Module 1. Git)
    ├──__init__.py # library entry point
    ├──__main__.py # CLI entry point
    ├──__version__.py 
    ├── pwgen.py # library implementation
    ├── cli/
    │   ├──__init__.py
    │   ├──__main__.py
    │   └── cli.py # CLI code implementation
    │
    └── showcase/
        ├──__init__.py 
        ├──__main__.py # showcase entry point when using python -m showcase
        ├── pattern-list.txt # input pattern examples file 
        ├── pattern-list-error.txt # input pattern examples file with intentional errors
        └── showcase.py # showcase implementationInstallationUse your preferred installation method via different package installation managers to install Pwgen.PipTo install Pwgen packet to your environment using pip manager invokepip install task2a.$pipinstalltask2a
Collectingtask2aUsingcachedtask2a-0.1.N-py3-none-any.whl(10kB)Collectingquestionary<2.0.0,>=1.10.0(fromtask2a)Downloadingquestionary-1.10.0-py3-none-any.whl(31kB)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━31.1/31.1kBN.NMB/seta0:00:00
Collectingprompt_toolkit<4.0,>=2.0(fromquestionary<2.0.0,>=1.10.0->task2a)Downloadingprompt_toolkit-3.0.39-py3-none-any.whl(385kB)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━385.2/385.2kBN.NMB/seta0:00:00
Requirementalreadysatisfied:wcwidthin<yourenvironmentfolder>\.venv\lib\site-packages(fromprompt_toolkit<4.0,>=2.0->questionary<2.0.0,>=1.10.0->task2a)(0.2.6)Installingcollectedpackages:prompt_toolkit,questionary,task2a
Successfullyinstalledprompt_toolkit-3.0.39questionary-1.10.0task2a-0.1.NTo uninstall Pwgen from your environment invokepip uninstall task2a.$pipuninstalltask2a
Foundexistinginstallation:task2a0.1.N
Uninstallingtask2a-0.1.N:Wouldremove:<yourenvironmentfolder>\.venv\lib\site-packages\task2a-0.1.N.dist-info\*<yourenvironmentfolder>\.venv\lib\site-packages\task2a\*<yourenvironmentfolder>\.venv\scripts\pwgen.exe<yourenvironmentfolder>\.venv\scripts\pwgen_showcase.exe
Proceed(Y/n)?ySuccessfullyuninstalledtask2a-0.1.NIt's important to note that the pip manager does not uninstall dependent packages. Therefore, if you wish to remove them, you'll need to take
the initiative and perform the task yourself. You can do this by using the commandspip uninstall questionaryandpip uninstall prompt-toolkit.$pipuninstallquestionary
Foundexistinginstallation:questionary1.10.0
Uninstallingquestionary-1.10.0:Wouldremove:<yourenvironmentfolder>\.venv\lib\site-packages\questionary-1.10.0.dist-info\*<yourenvironmentfolder>\.venv\lib\site-packages\questionary\*Proceed(Y/n)?ySuccessfullyuninstalledquestionary-1.10.0$pipuninstallprompt-toolkit
Foundexistinginstallation:prompt-toolkit3.0.39
Uninstallingprompt-toolkit-3.0.39:Wouldremove:<yourenvironmentfolder>\.venv\lib\site-packages\prompt_toolkit-3.0.39.dist-info\*<yourenvironmentfolder>\.venv\lib\site-packages\prompt_toolkit\*Proceed(Y/n)?ySuccessfullyuninstalledprompt-toolkit-3.0.39PoetryTo install Pwgen packet to your environment using poetry manager invokepoetry add task2a.$poetryaddtask2a
Usingversion^0.1.Nfortask2a

Updatingdependencies
Resolvingdependencies...

Packageoperations:4installs,0updates,0removals•Installingwcwidth(0.2.6)•Installingprompt-toolkit(3.0.39)•Installingquestionary(1.10.0)•Installingtask2a(0.1.N)WritinglockfileBy taking this action, a new dependency line will be added to your .toml file.[tool.poetry.dependencies]task2a="^0.1.N"To uninstall Pwgen from your environment invokepoetry remove task2a.
One of the benefits of utilizing Poetry is that it allows for the removal of all dependent packages with a single command.$poetryremovetask2a
Updatingdependencies
Resolvingdependencies...

Packageoperations:0installs,0updates,4removals•Removingprompt-toolkit(3.0.39)•Removingquestionary(1.10.0)•Removingtask2a(0.1.N)•Removingwcwidth(0.2.6)WritinglockfileUsageThere are various ways to use this library, as mentioned earlier.Utilize it like a library you can just import it into your .py file and use generate password methods within your code.Utilize CLI via the command shell, either as a Python module or as a standalone command.Utilize CLI command in a pipe by passing stdout of other commands to the stdin of the pwgen command, writing stdout and stderr to files, or passing them to following commands.The library contains rich showcase command that allows you to test all the use cases and even perform them in batches.LibraryBelow is a code snippet that demonstrates how to be able to use the pwgen library in your code.importloggingfromtask2a.pwgenimportgenerate_passwordsclassUser:def__init__(self,login:str,name:str,surname:str,department:Optional[str]=None,phone:Optional[str]=None,one_time_pwd:bool=False,pw_charset:Optional[str]=None,pw_template:Optional[str]=None):self.login=loginself.name=nameself.surname=surnameself.department=departmentself.phone=phoneifone_time_pwd:ifpw_charset:self.password=generate_passwords(placeholders_set=pw_charset)elifpw_pattern:self.password=generate_passwords(template=pw_template)else:self.password=generate_passwords()# Interactively gather user informationlogin=input("Enter login: ")name=input("Enter name: ")surname=input("Enter surname: ")department=input("Enter department: ")phone=input("Enter phone: ")# Create an instance of the User classuser=User(login,name,surname,department,phone)CLIThe CLI interface has a single command called "pwgen." It can be invoked using two methods:python -m pwgenor simplypwgen.
Pwgen accepts various arguments, which are described in the "ARGUMENT" dictionary.ARGUMENTS={'n':Argument('Password length','Set length of password and generate random password from character sets',metavar='',type=int,default=8),'t':Argument('Password pattern','Set template for generating passwords',metavar='',type=str),'f':Argument('File with list of patterns','Get list of patterns from file and generate passwords for each pattern',metavar='',type=str),'c':Argument('Number of passwords','Number of passwords to generate',metavar='',type=int,default=1),'S':Argument('Custom character set','Define custom character set',metavar='',type=str),'p':Argument('Permutation','Randomly permute characters of password',action='store_true'),'v':Argument('Verbosity level','Increase verbosity level',action='count',default=0),}There are some rules for argument handling:if you pass argument -f, the -t and -S arguments will be ignoredif you pass argument -t, the -S arguments will be ignoredif no -f, -t, -S arguments are passed, passwords are generated based on the default character setWhen using input files or stdin via the pipe, it's important to pass patterns or lists of patterns only (avoid character sets and other variations).
Each line in the file will be treated as one pattern.To handle the arguments, theargparsemodule is used. If you are already acquainted with it, you will have no difficulty in passing the arguments along with their values and comprehending their behavior.Here is an example list of CLI using, more examples in a more convenient form you could find in theshowcase:Call exampleOutcome explanationpwgenorpython -m pwgenOne eight-character (default value) password will be generated based on the default character set.pwgen -n5orpwgen -n 5One five-character password will be generated based on the default character set.pwgen -c3Three eight-character passwords will be generated based on the default character set.pwgen -n5 -c3 -pThree five-character passwords will be generated based on the default character set and permutate.pwgen -n5 -c3 -vThree five-character passwords will be generated based on the default character set. Warning messages will also be displayed during the generation.pwgen -n5 -c3 -vvThree five-character passwords will be generated based on the default character set. Warning and info messages will also be displayed during the generation.pwgen -n5 -c3 -vvvThree five-character passwords will be generated based on the default character set. All types of messages will be displayed during the generation.pwgen -f .venv/Lib/site-packages/pwgen/test/pattern-list.txtN passwords (based on text lines in the file) will be generated based on each line given template. Pattern length sets the password length.pwgen -f .venv/Lib/site-packages/pwgen/test/pattern-list.txt -n5 -c3N * 3 passwords will be generated based on each line given template. The -n flag will be ignored.pwgen -t u{2}p{5}l{2}d{2}L -n5 -c3 -pThree passwords will be generated based on a given template and will be permutate after the generation. The -n flag will be ignored.pwgen -S Ld^l^\\4^\\5^\\6^\\7^\\8 -n5 -c3Three five-character passwords will be generated based on the given character set. (Please note that in certain cases, you may need to double the \ symbol to prevent any errors in execution).pwgen -t u{2}p{5}l{2}d{2}L -S Ld^l^\\4^\\5^\\6^\\7^\\8 -n5 -c3N * 3 passwords will be generated based on each line given template. The -n and -s flags will be ignored.Pipes and filesThe pwgen command could be used inside the pipe of the BASH commands. It can be used in various ways within a pipeline:receiving inputdirect output to a filedirect logging also to a filedirect output to the next command in the pipelineHere is an example list of pipeline using, more examples in a more convenient form you could find in theshowcase:Call exampleOutcome explanationcat .venv/Lib/site-packages/pwgen/test/pattern-list.txt | pwgen -n5 -c3orcat .venv/Lib/site-packages/pwgen/test/pattern-list.txt | python -m pwgen -n5 -c3A template list is taken from a file and passed as input to pwgen.  Three passwords on each template will be generated. The -n flag will be ignored.pwgen -S "Ld^l^\\4^\\5^\\6^\\7^\\8" -n5 -c3 -vv > ./pwd-lst.txtThree five-character passwords will be generated based on the given character set. The generated passwords will not be displayed on the screen and will be saved to a file. Warning and info messages will also be displayed during the generation.pwgen -S "Ld^l^\\4^\\5^\\6^\\7^\\8" -n5 -c3 -vvv 2> ./pwgen.logThree five-character passwords will be generated based on the given character set. The generated passwords will be displayed. All types of messages will not be displayed during the generation and will be saved to a file.echo u{4}[Ld^l^\\4^\\5^\\6^\\7^\\8]{3}l{2} | pwgen -c6 -n5 | sort -rThe echo message will not be displayed and passed as input to pwgen. Six passwords will be generated based on the echo template, will not be displayed, and passed as input to the sort command. The sort command will sort passwords in reverse order and print them out. The -n flag will be ignored.ShowcaseTo showcase the behavior of the pwgen library, an interactive command called "pwgen_showcase" has been created.
This command utilizes both the pwgen CLI and the pwgen library. It's an interactive command you can invoke viapwgen_showcaseorpython -m pwgen.showcase.
It has an optional flag that allows you to view all use cases at once without any interaction.
You can use the commandpwgen_showcase --allto activate this feature.General provisionsAll materials provided and/or made available contain EPAM’s proprietary and confidential information and must not to be copied,
reproduced or disclosed to any third party or to any other person, other than those persons who have a bona fide need to review it
for the purpose of participation in the online courses being provided by EPAM.
The intellectual property rights in all materials (including any trademarks) are owned by EPAM Systems Inc or its associated companies,
and a limited license, terminable at the discretion of EPAM without notice, is hereby granted to you solely for the purpose of participating
in the online courses being provided by EPAM. Neither you nor any other party shall acquire any intellectual property rights of any kind
in such materials.