Library for generating passwords
1. Add even more typing
It's good that you use some basic type annotations but you should use them everywhere, not just on a few select parameters. Have a look at the typing module, especially if you want to support Python versions before 3.9. For example, your function signatures could be
def __init__(self, length: int, characters: Optional[str] = None):
def generate(self, num_of_passwords: int) -&gt; None:
def __init__(self, length: int, string_method: Literal[&quot;upper&quot;, &quot;lower&quot;, &quot;both&quot;], numbers: bool = True, special_chars: bool = False):

2. Keyword-only parameters
This is a personal suggestion and I am sure you will find people who disagree with me on it. In your current code, Complex(5, &quot;lower&quot;, True, False) is a valid call; yet, it is completely meaningless to a reader. By making the last two parameters keyword-only, you force the user to be more expressive: Complex(5, &quot;lower&quot;, numbers=True, special_chars=False).
On that note, I suggest to give the parameters more expressive names such as include_numbers and include_special_characters.
3. Add better docstrings
You should ask yourself: who do you write your code documentation for? Right now, comments like &quot;A simple password&quot; don't provide much helpful information to anyone except for maybe yourself. What makes a password &quot;simple&quot;? Does it only contain numbers and letters? Is it short? Is it easy to remember? The same holds true for &quot;complex&quot;, &quot;memorable&quot; and other terms. I'll do a shameless plug of my own recent blog post on that topic for more information.
4. Security
In the context of &quot;passwords&quot;, &quot;security&quot; should always be mentioned, even if you feel that for your particular purpose you don't actually need it. Toby Speight already commented that getting words from a dictionary in the web with a hardcoded URL gives a pretty good target for attackers. An easy and platform-independent workaround would be to include a complete word dictionary in your package itself so there is no need to get it via HTTP.
Another issue might come up due to the fact that random, which you use to build your passwords, is not cryptographically secure. The secrets module, which you also used, is already a better alternative.
5. Enums
This point, again, might be a bit controversial. Instead of using &quot;method&quot; strings, &quot;upper&quot;, &quot;lower&quot;, and &quot;both&quot;, I'd suggest to use an enum class instead, which makes it easier for your IDE to help you and gives you errors more quickly should you try to use a value that does not exist.
class PasswordGenerationStrategy(enum.Enum):
  Upper = &quot;upper&quot;
  Lower = &quot;lower&quot;
  Both = &quot;both&quot;