passmaker 0.1.1__tar.gz → 0.2__tar.gz

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.
@@ -1,24 +1,26 @@
1
- name: Python package
1
+ name: Publish new package version
2
2
 
3
3
  on:
4
4
  push:
5
5
  # On version pushes
6
6
  tags:
7
7
  - 'v[0-9]+.[0-9]+.[0-9]+'
8
+ - 'v[0-9]+.[0-9]+'
8
9
 
9
10
  workflow_dispatch:
10
11
 
11
- permissions:
12
- contents: write
13
-
14
12
  jobs:
15
13
  verify-checks:
16
- uses: ./.github/workflows/static-checks.yaml
14
+ uses: ./.github/workflows/quality-assurance.yaml
17
15
 
18
16
  publish:
19
17
  name: Build and publish Python Package
20
18
  needs: verify-checks
21
19
  runs-on: ubuntu-latest
20
+ permissions:
21
+ id-token: write
22
+ contents: write
23
+ environment: release
22
24
 
23
25
  steps:
24
26
  - uses: actions/checkout@v4
@@ -41,7 +43,7 @@ jobs:
41
43
  shell: bash
42
44
  run: |
43
45
  VERSION="$(hatchling version)"
44
- if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
46
+ if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ && ! "$VERSION" =~ ^[0-9]+\.[0-9]+$ ]]; then
45
47
  echo "::error::Expected a pure release version, got: $VERSION"
46
48
  exit 1
47
49
  fi
@@ -52,8 +54,6 @@ jobs:
52
54
  uv build
53
55
 
54
56
  - name: Publish package to PyPI
55
- env:
56
- UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
57
57
  run: |
58
58
  uv publish
59
59
 
@@ -63,5 +63,5 @@ jobs:
63
63
  VERSION: ${{ env.VERSION }}
64
64
  run: |
65
65
  gh release create "v$VERSION" \
66
- --generate-notes \
67
- --verify-tag
66
+ --generate-notes --verify-tag \
67
+ ./dist/*.tar.gz ./dist/*.whl
@@ -1,8 +1,12 @@
1
- name: Static file checks
1
+ name: Run quality assurance checks
2
2
 
3
3
  on:
4
4
  pull_request:
5
- types: [opened, reopened, synchronize, ready_for_review]
5
+ types:
6
+ - opened
7
+ - reopened
8
+ - synchronize
9
+ - ready_for_review
6
10
 
7
11
  push:
8
12
  branches:
@@ -16,16 +20,17 @@ jobs:
16
20
  if: github.event.pull_request.draft == false
17
21
  runs-on: ubuntu-latest
18
22
  steps:
19
- - uses: actions/checkout@v4
23
+ - uses: actions/checkout@v7
20
24
 
21
25
  - name: Check formatting
22
- uses: astral-sh/ruff-action@v3
26
+ uses: astral-sh/ruff-action@v4.1.0
23
27
  with:
24
- args: format --check --diff --exclude=src/manager --exclude=src/connection
28
+ version-file: uv.lock
29
+ args: format --check --diff
25
30
 
26
31
  - name: Check linting
27
32
  run: |
28
- ruff check --output-format=github --extend-exclude=src/manager --extend-exclude=src/connection
33
+ ruff check --output-format=github
29
34
 
30
35
  type-checking:
31
36
  name: Static type checking
@@ -35,27 +40,26 @@ jobs:
35
40
  strategy:
36
41
  fail-fast: true
37
42
  matrix:
38
- python-version: ["3.14"]
43
+ python-version:
44
+ - "3.14"
39
45
 
40
46
  steps:
41
- - uses: actions/checkout@v4
47
+ - uses: actions/checkout@v7
42
48
 
43
- - uses: actions/setup-python@v4
49
+ - uses: actions/setup-python@v7
44
50
  with:
45
51
  python-version: ${{ matrix.python-version }}
46
52
 
47
53
  - name: Setup uv
48
- uses: astral-sh/setup-uv@v6
54
+ uses: astral-sh/setup-uv@v10.0.1
49
55
  with:
50
56
  activate-environment: true
51
- enable-cache: true
52
57
 
53
58
  - name: Synchronise dependencies
54
59
  run: |
55
60
  uv sync --locked --all-extras --all-groups --dev
56
61
 
57
62
  - name: Check with Pyright
58
- uses: jakebailey/pyright-action@v2
63
+ uses: jakebailey/pyright-action@v3
59
64
  with:
60
- version: latest
61
- annotate: all
65
+ version: PATH
@@ -0,0 +1,20 @@
1
+ repos:
2
+ - repo: local
3
+ hooks:
4
+ - id: pyrefly-check
5
+ name: Pyrefly Check
6
+ entry: uv run --dev pyrefly check
7
+ language: system
8
+ pass_filenames: false
9
+
10
+ - id: ruff-check
11
+ name: Ruff Check
12
+ entry: uv run --dev ruff check
13
+ language: system
14
+ files: .*[.]py[i]?$
15
+
16
+ - id: ruff-format
17
+ name: Ruff Format
18
+ entry: uv run --dev ruff format --check
19
+ language: system
20
+ files: .*[.]py[i]?$
passmaker-0.2/PKG-INFO ADDED
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.5
2
+ Name: passmaker
3
+ Version: 0.2
4
+ Summary: Generate strong passwords with ease and peace.
5
+ Author-email: Soucelover <mr.ktotov@gmail.com>
6
+ Requires-Python: >=3.14
7
+ Requires-Dist: randpal>=0.5
8
+ Provides-Extra: cli
9
+ Requires-Dist: click-option-group>=0.5.9; extra == 'cli'
10
+ Requires-Dist: click>=8.3.1; extra == 'cli'
@@ -12,6 +12,11 @@ dynamic = ["version"]
12
12
  # ===================
13
13
  requires-python = ">=3.14"
14
14
  dependencies = [
15
+ "randpal>=0.5",
16
+ ]
17
+
18
+ [project.optional-dependencies]
19
+ cli = [
15
20
  "click>=8.3.1",
16
21
  "click-option-group>=0.5.9",
17
22
  ]
@@ -19,7 +24,8 @@ dependencies = [
19
24
  [dependency-groups]
20
25
  dev = [
21
26
  "pre-commit>=4.6.0",
22
- "pyrefly>=1.0.0",
27
+ "pyrefly>=1.2.0",
28
+ "pyright>=1.1.411",
23
29
  "ruff>=0.15.15",
24
30
  ]
25
31
 
@@ -0,0 +1,23 @@
1
+ from .passgen import (
2
+ PasswordGenerator,
3
+ build_alphabet,
4
+ generate_passwords,
5
+ passgen,
6
+ )
7
+ from .rules import (
8
+ ALL_RULES,
9
+ no_duplicates,
10
+ no_sequence_characters,
11
+ no_similar_characters,
12
+ )
13
+
14
+ __all__ = [
15
+ "ALL_RULES",
16
+ "PasswordGenerator",
17
+ "build_alphabet",
18
+ "generate_passwords",
19
+ "no_duplicates",
20
+ "no_sequence_characters",
21
+ "no_similar_characters",
22
+ "passgen",
23
+ ]
@@ -0,0 +1,19 @@
1
+ def main() -> None:
2
+ try:
3
+ from .cli import cli # noqa: PLC0415
4
+ except ImportError as exc:
5
+ if exc.name is None or not exc.name.startswith("click"):
6
+ raise
7
+
8
+ print( # noqa: T201
9
+ "Missing optional dependency `cli`. Try installing with command:"
10
+ "\n\n pip install passmaker[cli]",
11
+ end="\n\n",
12
+ )
13
+ raise SystemExit(1) from exc
14
+
15
+ cli()
16
+
17
+
18
+ if __name__ == "__main__":
19
+ main()
@@ -0,0 +1,111 @@
1
+ # Optional depenencies group `cli` is required for:
2
+ # - click
3
+ # - click_option_group
4
+
5
+
6
+ import click
7
+ from click_option_group import optgroup
8
+
9
+ from .passgen import generate_passwords
10
+
11
+
12
+ @click.command()
13
+ @click.option(
14
+ "-l", "--length", default=22, type=int, help="Length of each password."
15
+ )
16
+ @click.option(
17
+ "-c",
18
+ "--count",
19
+ default=5,
20
+ type=int,
21
+ help="Count of passwords to generate in one take.",
22
+ )
23
+ @optgroup.group(
24
+ "Password alphabet",
25
+ help="Options controlling what set of symbols can be used in a password.",
26
+ )
27
+ @optgroup.option(
28
+ "-d/-D",
29
+ "--digits/--no-digits",
30
+ "use_digits",
31
+ default=True,
32
+ help="Where to use digits in password or no (default: yes)",
33
+ )
34
+ @optgroup.option(
35
+ "-a/-A",
36
+ "--alpha/--no-alpha",
37
+ "use_alpha",
38
+ default=True,
39
+ help="Where to use ASCII alphabet symbols in password or no "
40
+ "(default: yes)",
41
+ )
42
+ @optgroup.option(
43
+ "-S",
44
+ "--no-symbols",
45
+ default=False,
46
+ is_flag=True,
47
+ flag_value=True,
48
+ help="Don't use punctuation symbols for password generation "
49
+ "(overrides 'symbols' option).",
50
+ )
51
+ @optgroup.option(
52
+ "-s",
53
+ "--symbols",
54
+ type=str,
55
+ help="Use these exact symbols for password generation.",
56
+ )
57
+ @optgroup.group(
58
+ "Generation rules", help="Set of rules to control password generation."
59
+ )
60
+ @optgroup.option(
61
+ "--no-duplicates/--duplicates",
62
+ "use_duplicates",
63
+ default=True,
64
+ help="Allow or disallow duplicate characters.",
65
+ )
66
+ @optgroup.option(
67
+ "--no-sequence/--sequence",
68
+ "use_sequence",
69
+ default=True,
70
+ help="Allow or disallow sequential characters.",
71
+ )
72
+ @optgroup.option(
73
+ "--no-similar/--similar",
74
+ "use_similar",
75
+ default=True,
76
+ help="Allow or disallow similar looking characters.",
77
+ )
78
+ def cli( # noqa: PLR0913
79
+ length: int,
80
+ count: int,
81
+ *,
82
+ use_digits: bool,
83
+ use_alpha: bool,
84
+ symbols: str,
85
+ no_symbols: bool,
86
+ use_duplicates: bool,
87
+ use_sequence: bool,
88
+ use_similar: bool,
89
+ ) -> None:
90
+ """Simple password generator. Safe to use, generates quite strong
91
+ passwords.
92
+ """
93
+ if no_symbols and symbols:
94
+ msg = (
95
+ "The argument '--symbols=...' cannot be used with '--no-symbols'"
96
+ )
97
+ raise click.ClickException(msg)
98
+
99
+ passwords = generate_passwords(
100
+ length,
101
+ count,
102
+ use_digits=use_digits,
103
+ use_alpha=use_alpha,
104
+ symbols=False if no_symbols else symbols or True,
105
+ use_duplicates=use_duplicates,
106
+ use_sequence=use_sequence,
107
+ use_similar=use_similar,
108
+ )
109
+
110
+ for password in passwords:
111
+ click.echo(password)
@@ -0,0 +1,118 @@
1
+ import functools
2
+ import secrets
3
+ import string
4
+ from typing import TYPE_CHECKING
5
+
6
+ from randpal.rng import SecretsRandom
7
+
8
+ from .rules import (
9
+ ALL_RULES,
10
+ no_duplicates,
11
+ no_sequence_characters,
12
+ no_similar_characters,
13
+ )
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Iterable
17
+
18
+ from randpal.rng import SequencesGenerator
19
+
20
+ from .rules import Rule
21
+
22
+
23
+ class PasswordGenerator:
24
+ _random: SequencesGenerator
25
+ rules: tuple[Rule, ...]
26
+ alphabet: frozenset[str]
27
+
28
+ def __init__(
29
+ self,
30
+ alphabet: str | None = None,
31
+ *,
32
+ rules: Iterable[Rule] = ALL_RULES,
33
+ random_provider: SequencesGenerator | None = None,
34
+ ) -> None:
35
+ self._random = random_provider or SecretsRandom()
36
+ self.rules = tuple(rules)
37
+
38
+ if alphabet is None:
39
+ self.alphabet = frozenset(build_alphabet())
40
+ else:
41
+ self.alphabet = frozenset(alphabet)
42
+
43
+ def gen_password(self, length: int) -> str:
44
+ password = ""
45
+
46
+ for _ in range(length):
47
+ allowed_symbols = functools.reduce(
48
+ lambda alpha, rule: rule(password, alpha),
49
+ self.rules,
50
+ self.alphabet,
51
+ )
52
+ password += secrets.choice(list(allowed_symbols))
53
+
54
+ return password
55
+
56
+
57
+ def passgen(
58
+ length: int,
59
+ alphabet: str | None = None,
60
+ rules: Iterable[Rule] = ALL_RULES,
61
+ ) -> str:
62
+ generator = PasswordGenerator(alphabet, rules=rules)
63
+ return generator.gen_password(length)
64
+
65
+
66
+ def build_alphabet(
67
+ *,
68
+ digits: bool = True,
69
+ lowercase: bool = True,
70
+ uppercase: bool = True,
71
+ symbols: bool | str = True,
72
+ ) -> str:
73
+ alphabet = ""
74
+
75
+ if digits:
76
+ alphabet += string.digits
77
+ if lowercase:
78
+ alphabet += string.ascii_lowercase
79
+ if uppercase:
80
+ alphabet += string.ascii_uppercase
81
+ if symbols:
82
+ alphabet += (
83
+ symbols if isinstance(symbols, str) else string.punctuation
84
+ )
85
+
86
+ return alphabet
87
+
88
+
89
+ def generate_passwords( # noqa: PLR0913
90
+ length: int = 22,
91
+ count: int = 5,
92
+ *,
93
+ use_digits: bool = True,
94
+ use_alpha: bool = True,
95
+ symbols: str | bool = True,
96
+ use_duplicates: bool = True,
97
+ use_sequence: bool = True,
98
+ use_similar: bool = True,
99
+ ) -> list[str]:
100
+ alphabet = build_alphabet(
101
+ digits=use_digits,
102
+ lowercase=use_alpha,
103
+ uppercase=use_alpha,
104
+ symbols=symbols,
105
+ )
106
+
107
+ rules: list[Rule] = []
108
+
109
+ if use_duplicates:
110
+ rules.append(no_duplicates)
111
+ if use_sequence:
112
+ rules.append(no_sequence_characters)
113
+ if use_similar:
114
+ rules.append(no_similar_characters)
115
+
116
+ generator = PasswordGenerator(alphabet, rules=rules)
117
+
118
+ return [generator.gen_password(length) for _ in range(count)]
@@ -0,0 +1,66 @@
1
+ import contextlib
2
+ from collections.abc import Callable
3
+ from collections.abc import Set as AbstractSet
4
+
5
+ # Rule is a filter to an alphabet
6
+ type Rule = Callable[[str, AbstractSet[str]], AbstractSet[str]]
7
+ QWERTY: str = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
8
+
9
+
10
+ def no_duplicates(
11
+ password: str, alphabet: AbstractSet[str]
12
+ ) -> AbstractSet[str]:
13
+ """Don't use the same character more than once in a password.
14
+
15
+ This rule removes from alphabet every char from password.
16
+ """
17
+ if not password:
18
+ return alphabet
19
+
20
+ return alphabet - set(password)
21
+
22
+
23
+ def no_sequence_characters(
24
+ password: str, alphabet: AbstractSet[str]
25
+ ) -> AbstractSet[str]:
26
+ """Don't use characters in password in a sequence, e.g. abc, 123, etc.
27
+
28
+ This rule enforces that the next symbol in password cannot be from
29
+ sequence, thus it's removed from alphabet.
30
+ """
31
+
32
+ def _get_char(string: str, idx: int) -> str:
33
+ try:
34
+ return string[idx]
35
+ except IndexError:
36
+ return ""
37
+
38
+ if not password:
39
+ return alphabet
40
+
41
+ with contextlib.suppress(ValueError):
42
+ index = QWERTY.index(password[-1])
43
+
44
+ alphabet -= {
45
+ _get_char(QWERTY, index - 1),
46
+ _get_char(QWERTY, index + 1),
47
+ }
48
+
49
+ return alphabet - {chr(ord(password[-1]) - 1), chr(ord(password[-1]) + 1)}
50
+
51
+
52
+ def no_similar_characters(
53
+ password: str, alphabet: AbstractSet[str]
54
+ ) -> AbstractSet[str]:
55
+ """Don't use similar characters in password, e.g. i, I, L, l, 1, etc.
56
+
57
+ This rule removes from alphabet these characters: i1Il|, oO0, `.:
58
+ """
59
+ return alphabet - set("i1Il|oO0`.:")
60
+
61
+
62
+ ALL_RULES: list[Rule] = [
63
+ no_duplicates,
64
+ no_sequence_characters,
65
+ no_similar_characters,
66
+ ]
@@ -84,6 +84,11 @@ wheels = [
84
84
  name = "passmaker"
85
85
  source = { editable = "." }
86
86
  dependencies = [
87
+ { name = "randpal" },
88
+ ]
89
+
90
+ [package.optional-dependencies]
91
+ cli = [
87
92
  { name = "click" },
88
93
  { name = "click-option-group" },
89
94
  ]
@@ -92,19 +97,23 @@ dependencies = [
92
97
  dev = [
93
98
  { name = "pre-commit" },
94
99
  { name = "pyrefly" },
100
+ { name = "pyright" },
95
101
  { name = "ruff" },
96
102
  ]
97
103
 
98
104
  [package.metadata]
99
105
  requires-dist = [
100
- { name = "click", specifier = ">=8.3.1" },
101
- { name = "click-option-group", specifier = ">=0.5.9" },
106
+ { name = "click", marker = "extra == 'cli'", specifier = ">=8.3.1" },
107
+ { name = "click-option-group", marker = "extra == 'cli'", specifier = ">=0.5.9" },
108
+ { name = "randpal", specifier = ">=0.5" },
102
109
  ]
110
+ provides-extras = ["cli"]
103
111
 
104
112
  [package.metadata.requires-dev]
105
113
  dev = [
106
114
  { name = "pre-commit", specifier = ">=4.6.0" },
107
- { name = "pyrefly", specifier = ">=1.0.0" },
115
+ { name = "pyrefly", specifier = ">=1.2.0" },
116
+ { name = "pyright", specifier = ">=1.1.411" },
108
117
  { name = "ruff", specifier = ">=0.15.15" },
109
118
  ]
110
119
 
@@ -135,19 +144,34 @@ wheels = [
135
144
 
136
145
  [[package]]
137
146
  name = "pyrefly"
138
- version = "1.0.0"
147
+ version = "1.2.0"
148
+ source = { registry = "https://pypi.org/simple" }
149
+ sdist = { url = "https://files.pythonhosted.org/packages/89/01/a86e9f24722b095c3f88e3616132b75a21b0df53804bdc6a45314dd4d93c/pyrefly-1.2.0.tar.gz", hash = "sha256:5485f960fc2481617068c918335c39ab1507ef90b6b5bd35bf57726e60e73185", size = 6243654, upload-time = "2026-08-01T02:56:27.592Z" }
150
+ wheels = [
151
+ { url = "https://files.pythonhosted.org/packages/7d/9d/3c0ef1d4843987b22f996ed381ec9cf5a3b1273e29804db276252e4c95eb/pyrefly-1.2.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7f46d983ac49ddd2b043694960a01dc6a19a5cfd8eec609d6bd9c42866f91b4e", size = 14026305, upload-time = "2026-08-01T02:56:02.611Z" },
152
+ { url = "https://files.pythonhosted.org/packages/0a/06/03bbb78fbea54cdc65b626619f3597d5611aca4fdef11e72a4e8360e7e63/pyrefly-1.2.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:756f669b5555090f5c1a4fef30db1785fabe657764f7e4e6dc88994dfb8ca82d", size = 13463880, upload-time = "2026-08-01T02:56:04.93Z" },
153
+ { url = "https://files.pythonhosted.org/packages/13/5a/7d8bc00a38e93bbc9c3e7bd14d305f7948717e667c9bcddeab9dd42fd255/pyrefly-1.2.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3465812ce5ef4781fb592edbf2724547296f0a3124be115d73c7e8b2401862d", size = 13907329, upload-time = "2026-08-01T02:56:07.104Z" },
154
+ { url = "https://files.pythonhosted.org/packages/be/94/9e08b4bf799d0b8f36b55a2783c7ba5f51730cf0632a85a67b5b5ed876cd/pyrefly-1.2.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5de7b2ad2bba5c8055181681a84b74143eac2234a48ba5d1b7ed7e7a722b02bd", size = 15039020, upload-time = "2026-08-01T02:56:09.208Z" },
155
+ { url = "https://files.pythonhosted.org/packages/5b/bd/bca5fd0c80f4daf8ee6903a29df9f3de1feb05ff0946b8f35ec8c5096b13/pyrefly-1.2.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25822ea9505f589ea8a725e4268b475132fb89e038fbf092e446510443ac142a", size = 14986199, upload-time = "2026-08-01T02:56:11.924Z" },
156
+ { url = "https://files.pythonhosted.org/packages/97/f7/f07087f3d185ad2eced0c56cef89ca5474dfb4ff25f146cd50a861c97553/pyrefly-1.2.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90efe75e17491ef5d636e10469e9278d7d0256b3b4c5e1f4750069bf3ae0f5d1", size = 14393715, upload-time = "2026-08-01T02:56:14.143Z" },
157
+ { url = "https://files.pythonhosted.org/packages/d3/70/0d142c320e284b9e3ce35e9b1e58b8ce2ee1f578f2a7234bc30e5022b94f/pyrefly-1.2.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:368aaf7eee4f511ddc0f8e564cf14e01ab2f10b0db9105c6d5b153bf498d07bf", size = 13933008, upload-time = "2026-08-01T02:56:16.525Z" },
158
+ { url = "https://files.pythonhosted.org/packages/5d/e8/e84f11b6e1f63fd453ad3654213b9a0f6f4de8cef6b58038eef2d0d5955d/pyrefly-1.2.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d52d5da7bc65fb7675fbaa80eda879d4f8787c494f04cac21603330d3abbdbbe", size = 14431827, upload-time = "2026-08-01T02:56:18.645Z" },
159
+ { url = "https://files.pythonhosted.org/packages/0f/06/810d31380f66c75e1c0779a408d3b16117b1b368b57894f6aa66bef21686/pyrefly-1.2.0-py3-none-win32.whl", hash = "sha256:8c90751de8506d938e8f802659c74cf35bd7a0036510ee6c634a38eebb280bfa", size = 13229447, upload-time = "2026-08-01T02:56:20.921Z" },
160
+ { url = "https://files.pythonhosted.org/packages/ed/98/4dafa3c7a1caed2dc8cc708dde09ba27963c7736508f55b626fff3024113/pyrefly-1.2.0-py3-none-win_amd64.whl", hash = "sha256:8a8964c224ccc4882730130955815de21ff443c1ac3f0b90685b19bf63848170", size = 14087387, upload-time = "2026-08-01T02:56:23.188Z" },
161
+ { url = "https://files.pythonhosted.org/packages/1b/1c/df3cb0a2e5591660ded7a1836cd2f29dc48c91adb1c0a3a700a96f6d09e1/pyrefly-1.2.0-py3-none-win_arm64.whl", hash = "sha256:3a90bb8df39dfbac74b1f3b2e9d7c526b8f80568884c3944d955023a73ebf61e", size = 13430873, upload-time = "2026-08-01T02:56:25.425Z" },
162
+ ]
163
+
164
+ [[package]]
165
+ name = "pyright"
166
+ version = "1.1.411"
139
167
  source = { registry = "https://pypi.org/simple" }
140
- sdist = { url = "https://files.pythonhosted.org/packages/9f/3a/9045b0097ac58979c7c30a4fa0e673db942d4adbc7b6d439bd54ae58c441/pyrefly-1.0.0.tar.gz", hash = "sha256:5c2b810ffcebd84be71de5df1223651edee951653a66935c6f091e957c452455", size = 5677995, upload-time = "2026-05-12T20:12:46.812Z" }
168
+ dependencies = [
169
+ { name = "nodeenv" },
170
+ { name = "typing-extensions" },
171
+ ]
172
+ sdist = { url = "https://files.pythonhosted.org/packages/7e/ab/265f7dc69d28113ebba19092e57b075f41543b2ed048429c5f56e2b88eac/pyright-1.1.411.tar.gz", hash = "sha256:d885a0551f2e763b089a02702174e7f4ba77548cddabc972ab86d1f7f1b0f998", size = 4112861, upload-time = "2026-06-25T02:14:06.37Z" }
141
173
  wheels = [
142
- { url = "https://files.pythonhosted.org/packages/f4/c6/90788819bac9c61dd7bacba53b79f3c12d47ccbe5e51b3d6d89f2387e1d2/pyrefly-1.0.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e355a0908555348ed4b9585ef25c76ff566673e345c866c325f1633f44d890b6", size = 13122950, upload-time = "2026-05-12T20:12:20.711Z" },
143
- { url = "https://files.pythonhosted.org/packages/82/91/a3cf2a1e87d336eaa804a1e6fc93266faf6dc2a97eecdbc7eae289628022/pyrefly-1.0.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a7038efc3a40f8294edee339895633cf22db268c0d434cdbcbefc34f78a9ecc3", size = 12599494, upload-time = "2026-05-12T20:12:23.495Z" },
144
- { url = "https://files.pythonhosted.org/packages/cd/ab/74d1e11e737e99b1c003ecc5d7d2e846c4ea1f328966bfdbbd0ac63fad0a/pyrefly-1.0.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da331ca515ed1c08791da2b5f664cf9c1294c48fd802133262e7d5d51e0f4416", size = 12995507, upload-time = "2026-05-12T20:12:25.951Z" },
145
- { url = "https://files.pythonhosted.org/packages/7c/ac/2df0899f8464c97e5d995f994c97c5cb5b0f58610432aa90d26d924e1db5/pyrefly-1.0.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c74219d8f3e63cdaa5501a0b21d1c9d37011820f9606728d0ed06f09ae86a878", size = 13947693, upload-time = "2026-05-12T20:12:29.188Z" },
146
- { url = "https://files.pythonhosted.org/packages/6b/3e/b247c24321e36f04b7d51f9ccf3df93e5009e4b29939524b36ec2e17dc2a/pyrefly-1.0.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0d05543b1bb6ee6d64149eb5d6b2fb15aa72d3962d6a97abca0afaca8b0c131", size = 13925803, upload-time = "2026-05-12T20:12:31.904Z" },
147
- { url = "https://files.pythonhosted.org/packages/61/16/cfa2d61a4aa1e1f7bca48bb37acd01c6a09db4864b16a54f9587092765ff/pyrefly-1.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1382d5b1fcdb49a4de9f34d112d2bddf290a78ff93ee8149492ad5f1077ddffc", size = 13470398, upload-time = "2026-05-12T20:12:35.302Z" },
148
- { url = "https://files.pythonhosted.org/packages/cb/2b/6372c7dddb326223e24a46b17efd0d4bd7b4fe22c821e523157577eed2d2/pyrefly-1.0.0-py3-none-win32.whl", hash = "sha256:aa8b5d0e47080e3202a2547b39f7a5a61d2c781c712b3b67884f745ca2c759d2", size = 12222643, upload-time = "2026-05-12T20:12:38.618Z" },
149
- { url = "https://files.pythonhosted.org/packages/be/ad/1d23be700b6b2ddaeb362360c7145917a8edbbf7240ae428d40541772fce/pyrefly-1.0.0-py3-none-win_amd64.whl", hash = "sha256:c8abcb0f2082e83c890375128f9cff4aa4d3f210b85eea7b3046c1ae764e77f5", size = 13146369, upload-time = "2026-05-12T20:12:41.423Z" },
150
- { url = "https://files.pythonhosted.org/packages/8c/38/16589134f3012fd097a10dcc85771555f1a5fb76e04b682597180743af30/pyrefly-1.0.0-py3-none-win_arm64.whl", hash = "sha256:d150fa9e40e8392832be81c3bcfc0497c146674ce4d0f8e04e1ec29e775ffb8c", size = 12538326, upload-time = "2026-05-12T20:12:43.996Z" },
174
+ { url = "https://files.pythonhosted.org/packages/0a/49/385be530a6a5b78d1cbcd5c2e38debc8959a2fc6bdb716f4e581002979fc/pyright-1.1.411-py3-none-any.whl", hash = "sha256:dc7c72a8e2700c55baa127554040e067041ea53ccfd50bf96308cc4291c7d5d9", size = 6181526, upload-time = "2026-06-25T02:14:04.691Z" },
151
175
  ]
152
176
 
153
177
  [[package]]
@@ -189,6 +213,15 @@ wheels = [
189
213
  { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
190
214
  ]
191
215
 
216
+ [[package]]
217
+ name = "randpal"
218
+ version = "0.5"
219
+ source = { registry = "https://pypi.org/simple" }
220
+ sdist = { url = "https://files.pythonhosted.org/packages/02/10/41011d552baa59a6990424a6bf2d00b3fbbe881b98a301263967d0467fa2/randpal-0.5.tar.gz", hash = "sha256:178e85ef26ea3e322d86cf9b44ad0d2210e6cb871444548d14e896d6d1e6c395", size = 30281, upload-time = "2026-09-04T14:43:57.251Z" }
221
+ wheels = [
222
+ { url = "https://files.pythonhosted.org/packages/03/62/22326f19d18c34a6064ac29bf662950a06ff3e0c3cd6f667531ee08df931/randpal-0.5-py3-none-any.whl", hash = "sha256:2718ae64d05b51af2e01408e54c15171a3d467ab4655f3794976032a8705b574", size = 9099, upload-time = "2026-09-04T14:43:56.325Z" },
223
+ ]
224
+
192
225
  [[package]]
193
226
  name = "ruff"
194
227
  version = "0.15.15"
@@ -214,6 +247,15 @@ wheels = [
214
247
  { url = "https://files.pythonhosted.org/packages/4e/b2/920464c907b191e37469d477a1aa8bc048b8f36c4c1610dfa4ab87b39e18/ruff-0.15.15-py3-none-win_arm64.whl", hash = "sha256:3c8ceca6792f38196b8f589bc92eccd03eef286602da92e5dc05cc42ef6441b7", size = 11138498, upload-time = "2026-05-28T14:16:38.425Z" },
215
248
  ]
216
249
 
250
+ [[package]]
251
+ name = "typing-extensions"
252
+ version = "4.16.0"
253
+ source = { registry = "https://pypi.org/simple" }
254
+ sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
255
+ wheels = [
256
+ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
257
+ ]
258
+
217
259
  [[package]]
218
260
  name = "virtualenv"
219
261
  version = "21.4.1"
@@ -1,19 +0,0 @@
1
- repos:
2
- # Ruff hooks
3
- - repo: https://github.com/astral-sh/ruff-pre-commit
4
- rev: v0.15.5
5
- hooks:
6
- # Run the linter
7
- - id: ruff-check
8
- args: [--fix]
9
- # Run the formatter
10
- - id: ruff-format
11
-
12
- # Pyrefly hook
13
- - repo: local
14
- hooks:
15
- - id: pyrefly-check
16
- name: Pyrefly (type checking via uvx)
17
- entry: uv tool run --from pyrefly pyrefly check
18
- language: system
19
- pass_filenames: false
passmaker-0.1.1/PKG-INFO DELETED
@@ -1,8 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: passmaker
3
- Version: 0.1.1
4
- Summary: Generate strong passwords with ease and peace.
5
- Author-email: Soucelover <mr.ktotov@gmail.com>
6
- Requires-Python: >=3.14
7
- Requires-Dist: click-option-group>=0.5.9
8
- Requires-Dist: click>=8.3.1
File without changes
@@ -1,211 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import contextlib
4
- import functools
5
- import secrets
6
- import string
7
- from collections.abc import Callable
8
- from typing import TYPE_CHECKING
9
-
10
- import click
11
- from click_option_group import optgroup
12
-
13
- if TYPE_CHECKING:
14
- from collections.abc import Sequence
15
-
16
- # Rule is a filter to an alphabet
17
- type Rule = Callable[[str, set[str]], set[str]]
18
-
19
- QWERTY: str = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
20
-
21
-
22
- def no_duplicates(password: str, alphabet: set[str]) -> set[str]:
23
- """Don't use the same character more than once in a password.
24
-
25
- This rule removes from alphabet every char from password."""
26
- if not password:
27
- return alphabet
28
-
29
- return alphabet - set(password)
30
-
31
-
32
- def no_sequence_characters(password: str, alphabet: set[str]) -> set[str]:
33
- """Don't use characters in password in a sequence, e.g. abc, 123, etc.
34
-
35
- This rule enforces that the next symbol in password cannot be from
36
- sequence, thus it's removed from alphabet."""
37
-
38
- def _get_char(string: str, idx: int) -> str:
39
- try:
40
- return string[idx]
41
- except IndexError:
42
- return ""
43
-
44
- if not password:
45
- return alphabet
46
-
47
- with contextlib.suppress(ValueError):
48
- index = QWERTY.index(password[-1])
49
-
50
- alphabet -= {
51
- _get_char(QWERTY, index - 1),
52
- _get_char(QWERTY, index + 1),
53
- }
54
-
55
- return alphabet - {chr(ord(password[-1]) - 1), chr(ord(password[-1]) + 1)}
56
-
57
-
58
- def no_similar_characters(password: str, alphabet: set[str]) -> set[str]:
59
- """Don't use similar characters in password, e.g. i, I, L, l, 1, etc.
60
-
61
- This rule removes from alphabet these characters: i1Il|, oO0, `.:"""
62
- return alphabet - set("i1Il|oO0`.:")
63
-
64
-
65
- all_rules = [no_duplicates, no_sequence_characters, no_similar_characters]
66
-
67
-
68
- def build_alphabet(
69
- *,
70
- digits: bool = True,
71
- lowercase: bool = True,
72
- uppercase: bool = True,
73
- symbols: bool | str = True,
74
- ) -> str:
75
- alphabet = ""
76
-
77
- if digits:
78
- alphabet += string.digits
79
- if lowercase:
80
- alphabet += string.ascii_lowercase
81
- if uppercase:
82
- alphabet += string.ascii_uppercase
83
- if symbols:
84
- alphabet += (
85
- symbols if isinstance(symbols, str) else string.punctuation
86
- )
87
-
88
- return alphabet
89
-
90
-
91
- def generate_password(
92
- length: int,
93
- alphabet: str | None = None,
94
- rules: Sequence[Rule] = all_rules,
95
- ) -> str:
96
- if alphabet is None:
97
- alphabet = build_alphabet()
98
-
99
- password = ""
100
-
101
- for _ in range(length):
102
- allowed_symbols = functools.reduce(
103
- lambda alpha, rule: rule(password, alpha), rules, set(alphabet)
104
- )
105
-
106
- password += secrets.choice(list(allowed_symbols))
107
-
108
- return password
109
-
110
-
111
- @click.command()
112
- @click.option(
113
- "-l", "--length", default=22, type=int, help="Length of each password."
114
- )
115
- @click.option(
116
- "-c",
117
- "--count",
118
- default=1,
119
- type=int,
120
- help="Count of passwords to generate in one take.",
121
- )
122
- @optgroup.group(
123
- "Password alphabet",
124
- help="Options controlling what set of symbols can be used in a password.",
125
- )
126
- @optgroup.option(
127
- "-d/-D",
128
- "--digits/--no-digits",
129
- "use_digits",
130
- default=True,
131
- help="Where to use digits in password or no (default: yes)",
132
- )
133
- @optgroup.option(
134
- "-a/-A",
135
- "--alpha/--no-alpha",
136
- "use_alpha",
137
- default=True,
138
- help="Where to use ASCII alphabet symbols in password or no "
139
- "(default: yes)",
140
- )
141
- @optgroup.option(
142
- "-S",
143
- "--no-symbols",
144
- default=False,
145
- help="Don't use punctuation symbols for password generation "
146
- "(overrides 'symbols' option).",
147
- )
148
- @optgroup.option(
149
- "-s",
150
- "--symbols",
151
- type=str,
152
- help="Use these exact symbols for password generation.",
153
- )
154
- @optgroup.group(
155
- "Generation rules", help="Set of rules to control password generation."
156
- )
157
- @optgroup.option(
158
- "--no-duplicates/--duplicates",
159
- "use_duplicates",
160
- default=True,
161
- help="Allow or disallow duplicate characters.",
162
- )
163
- @optgroup.option(
164
- "--no-sequence/--sequence",
165
- "use_sequence",
166
- default=True,
167
- help="Allow or disallow sequential characters.",
168
- )
169
- @optgroup.option(
170
- "--no-similar/--similar",
171
- "use_similar",
172
- default=True,
173
- help="Allow or disallow similar looking characters.",
174
- )
175
- def main( # noqa: PLR0913
176
- length: int,
177
- count: int,
178
- *,
179
- use_digits: bool,
180
- use_alpha: bool,
181
- symbols: str,
182
- no_symbols: bool,
183
- use_duplicates: bool,
184
- use_sequence: bool,
185
- use_similar: bool,
186
- ) -> None:
187
- """Simple password generator. Safe to use, generates quite strong
188
- passwords.
189
- """
190
- alphabet = build_alphabet(
191
- digits=use_digits,
192
- lowercase=use_alpha,
193
- uppercase=use_alpha,
194
- symbols=False if no_symbols else symbols or True,
195
- )
196
-
197
- rules: list[Rule] = []
198
-
199
- if use_duplicates:
200
- rules.append(no_duplicates)
201
- if use_sequence:
202
- rules.append(no_sequence_characters)
203
- if use_similar:
204
- rules.append(no_similar_characters)
205
-
206
- for _ in range(count):
207
- print(generate_password(length, alphabet=alphabet, rules=rules)) # noqa: T201
208
-
209
-
210
- if __name__ == "__main__":
211
- main()
File without changes
File without changes