qools 0.0.0__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.
- qools-0.0.0/.github/workflows/docs.yml +26 -0
- qools-0.0.0/.github/workflows/lint.yml +17 -0
- qools-0.0.0/.github/workflows/new_version.yml +21 -0
- qools-0.0.0/.github/workflows/publish.yml +17 -0
- qools-0.0.0/.github/workflows/strictacode.yml +43 -0
- qools-0.0.0/.github/workflows/tests.yml +10 -0
- qools-0.0.0/.gitignore +211 -0
- qools-0.0.0/.qarium/ai/employees/developer.md +34 -0
- qools-0.0.0/.qarium/ai/employees/devops.md +29 -0
- qools-0.0.0/.qarium/ai/employees/lead.md +35 -0
- qools-0.0.0/.qarium/ai/employees/qa.md +40 -0
- qools-0.0.0/.qarium/ai/employees/tech-writer.md +27 -0
- qools-0.0.0/.strictacode.yml +3 -0
- qools-0.0.0/PKG-INFO +42 -0
- qools-0.0.0/README.md +19 -0
- qools-0.0.0/docs/api-reference.md +1 -0
- qools-0.0.0/docs/getting-started.md +1 -0
- qools-0.0.0/docs/index.md +3 -0
- qools-0.0.0/docs/overrides/main.html +13 -0
- qools-0.0.0/mkdocs.yml +38 -0
- qools-0.0.0/pyproject.toml +82 -0
- qools-0.0.0/qools/__init__.py +0 -0
- qools-0.0.0/qools/funcutils.py +27 -0
- qools-0.0.0/qools.egg-info/PKG-INFO +42 -0
- qools-0.0.0/qools.egg-info/SOURCES.txt +32 -0
- qools-0.0.0/qools.egg-info/dependency_links.txt +1 -0
- qools-0.0.0/qools.egg-info/requires.txt +9 -0
- qools-0.0.0/qools.egg-info/top_level.txt +1 -0
- qools-0.0.0/setup.cfg +4 -0
- qools-0.0.0/tests/__init__.py +0 -0
- qools-0.0.0/tests/conftest.py +1 -0
- qools-0.0.0/tests/qools/__init__.py +0 -0
- qools-0.0.0/tests/qools/test_funcutils.py +98 -0
- qools-0.0.0/tests/test_import.py +5 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Release docs
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- 0.0.x
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
deploy:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Configure Python
|
|
17
|
+
uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: 3.x
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: |
|
|
23
|
+
pip install -e ".[docs]"
|
|
24
|
+
|
|
25
|
+
- name: Build and Deploy
|
|
26
|
+
run: mkdocs gh-deploy --force
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Lint
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [0.0.x]
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [0.0.x]
|
|
7
|
+
jobs:
|
|
8
|
+
lint:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: astral-sh/ruff-action@v3
|
|
13
|
+
with:
|
|
14
|
+
args: check qools/ tests/
|
|
15
|
+
- uses: astral-sh/ruff-action@v3
|
|
16
|
+
with:
|
|
17
|
+
args: format --check qools/ tests/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Open New Version
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
type:
|
|
7
|
+
description: "New version type"
|
|
8
|
+
required: true
|
|
9
|
+
type: choice
|
|
10
|
+
options: [minor, major]
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
create-branch:
|
|
17
|
+
uses: qarium/ci/.github/workflows/library-new-version.yml@0.0.x
|
|
18
|
+
with:
|
|
19
|
+
type: ${{ inputs.type }}
|
|
20
|
+
secrets:
|
|
21
|
+
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Publish Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: write
|
|
8
|
+
id-token: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
release:
|
|
12
|
+
uses: qarium/ci/.github/workflows/library-publish.yml@0.0.x
|
|
13
|
+
with:
|
|
14
|
+
src-package: 'qools'
|
|
15
|
+
secrets:
|
|
16
|
+
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
17
|
+
ZAI_TOKEN: ${{ secrets.ZAI_TOKEN }}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Strictacode
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- 0.0.x
|
|
7
|
+
pull_request:
|
|
8
|
+
branches:
|
|
9
|
+
- 0.0.x
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
analyze:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
with:
|
|
17
|
+
fetch-depth: 0
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.13"
|
|
22
|
+
|
|
23
|
+
- uses: actions/cache@v4
|
|
24
|
+
with:
|
|
25
|
+
path: ~/.cache/pip
|
|
26
|
+
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
|
|
27
|
+
restore-keys: |
|
|
28
|
+
${{ runner.os }}-pip-
|
|
29
|
+
|
|
30
|
+
- uses: qarium/strictacode/.github/actions/analyze@0.0.x
|
|
31
|
+
with:
|
|
32
|
+
install-cmd: pip install strictacode
|
|
33
|
+
working-directory: qools
|
|
34
|
+
env:
|
|
35
|
+
STRICTACODE_SCORE: 40
|
|
36
|
+
STRICTACODE_RP: 40
|
|
37
|
+
STRICTACODE_SCORE_DIFF: 3
|
|
38
|
+
STRICTACODE_RP_DIFF: 5
|
|
39
|
+
STRICTACODE_OP: 40
|
|
40
|
+
STRICTACODE_IMB: 35
|
|
41
|
+
STRICTACODE_DENSITY: 30
|
|
42
|
+
STRICTACODE_OP_DIFF: 5
|
|
43
|
+
STRICTACODE_DENSITY_DIFF: 3
|
qools-0.0.0/.gitignore
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
#poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
#pdm.lock
|
|
116
|
+
#pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
#pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# SageMath parsed files
|
|
135
|
+
*.sage.py
|
|
136
|
+
|
|
137
|
+
# Environments
|
|
138
|
+
.env
|
|
139
|
+
.envrc
|
|
140
|
+
.venv
|
|
141
|
+
env/
|
|
142
|
+
venv/
|
|
143
|
+
ENV/
|
|
144
|
+
env.bak/
|
|
145
|
+
venv.bak/
|
|
146
|
+
|
|
147
|
+
# Spyder project settings
|
|
148
|
+
.spyderproject
|
|
149
|
+
.spyproject
|
|
150
|
+
|
|
151
|
+
# Rope project settings
|
|
152
|
+
.ropeproject
|
|
153
|
+
|
|
154
|
+
# mkdocs documentation
|
|
155
|
+
/site
|
|
156
|
+
docs/plans/
|
|
157
|
+
|
|
158
|
+
# mypy
|
|
159
|
+
.mypy_cache/
|
|
160
|
+
.dmypy.json
|
|
161
|
+
dmypy.json
|
|
162
|
+
|
|
163
|
+
# Pyre type checker
|
|
164
|
+
.pyre/
|
|
165
|
+
|
|
166
|
+
# pytype static type analyzer
|
|
167
|
+
.pytype/
|
|
168
|
+
|
|
169
|
+
# Cython debug symbols
|
|
170
|
+
cython_debug/
|
|
171
|
+
|
|
172
|
+
# PyCharm
|
|
173
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
174
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
175
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
176
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
177
|
+
.idea/
|
|
178
|
+
|
|
179
|
+
# Abstra
|
|
180
|
+
# Abstra is an AI-powered process automation framework.
|
|
181
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
182
|
+
# Learn more at https://abstra.io/docs
|
|
183
|
+
.abstra/
|
|
184
|
+
|
|
185
|
+
# Visual Studio Code
|
|
186
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
187
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
188
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
189
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
190
|
+
# .vscode/
|
|
191
|
+
|
|
192
|
+
# Ruff stuff:
|
|
193
|
+
.ruff_cache/
|
|
194
|
+
|
|
195
|
+
# PyPI configuration file
|
|
196
|
+
.pypirc
|
|
197
|
+
|
|
198
|
+
# Cursor
|
|
199
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
200
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
201
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
202
|
+
.cursorignore
|
|
203
|
+
.cursorindexingignore
|
|
204
|
+
|
|
205
|
+
# Marimo
|
|
206
|
+
marimo/_static/
|
|
207
|
+
marimo/_lsp/
|
|
208
|
+
__marimo__/
|
|
209
|
+
|
|
210
|
+
# Actions
|
|
211
|
+
.claude/
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Developer
|
|
2
|
+
|
|
3
|
+
## Config
|
|
4
|
+
|
|
5
|
+
| Setting | Value |
|
|
6
|
+
|-------------|-----------------------------|
|
|
7
|
+
| compile_cmd | python -m py_compile <file> |
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
|
|
11
|
+
### Conventions
|
|
12
|
+
|
|
13
|
+
| Rule | Description |
|
|
14
|
+
|------|-------------|
|
|
15
|
+
| typing alias | Use `import typing as t` instead of `from typing import ...` |
|
|
16
|
+
| No if/else in logic | Do not use if/else anywhere except module globals |
|
|
17
|
+
| No staticmethod classes | Classes with only static methods are modules — use modules instead |
|
|
18
|
+
| classmethod constructors | Use classmethod as alternative constructors (initializers) |
|
|
19
|
+
| No singleton classes | Singleton class is a module — use modules instead |
|
|
20
|
+
| LBYL over EAFP | Look Before You Leap is preferred over Easier to Ask Forgiveness |
|
|
21
|
+
| Relative imports | Use relative imports within the package |
|
|
22
|
+
| Aggregation over inheritance | Prefer aggregation/composition over inheritance |
|
|
23
|
+
| Docstrings in English | All docstrings must be written in English |
|
|
24
|
+
| Google-style docstrings | Use Google-style format: `Args:`, `Returns:`, `Raises:` sections with indented descriptions |
|
|
25
|
+
|
|
26
|
+
### Patterns
|
|
27
|
+
|
|
28
|
+
| Pattern | Description | Example |
|
|
29
|
+
|---------|-------------|---------|
|
|
30
|
+
|
|
31
|
+
## Lessons
|
|
32
|
+
|
|
33
|
+
| Problem | Why | How to prevent |
|
|
34
|
+
|---------|-----|----------------|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# DevOps
|
|
2
|
+
|
|
3
|
+
## Config
|
|
4
|
+
|
|
5
|
+
| Key | Value | Description |
|
|
6
|
+
|----------------|------------------|---------------------------------------------|
|
|
7
|
+
| ci_provider | github-actions | CI provider |
|
|
8
|
+
| trigger_branch | 0.0.x | Default branch for triggers |
|
|
9
|
+
| diff_range | HEAD~5 | Git diff range for auto-analysis in feature |
|
|
10
|
+
|
|
11
|
+
## Rules
|
|
12
|
+
|
|
13
|
+
### Workflow Registry
|
|
14
|
+
|
|
15
|
+
| Workflow | File | Trigger | Purpose |
|
|
16
|
+
|--------------|--------------------|---------------------------------|----------------------------------------|
|
|
17
|
+
| Lint | lint.yml | push/PR to 0.0.x | Ruff lint + format check |
|
|
18
|
+
| Tests | tests.yml | push/PR to 0.0.x | pytest on Python 3.10–3.14 matrix |
|
|
19
|
+
| Docs | docs.yml | push to 0.0.x | mkdocs gh-deploy --force |
|
|
20
|
+
| Publish | publish.yml | workflow_dispatch | Build + publish to PyPI + GitHub Release |
|
|
21
|
+
| New Version | new_version.yml | workflow_dispatch | Create X.Y.x branch, set as default |
|
|
22
|
+
| Strictacode | strictacode.yml | push/PR to 0.0.x | Code quality analysis |
|
|
23
|
+
|
|
24
|
+
### Conventions
|
|
25
|
+
|
|
26
|
+
## Lessons
|
|
27
|
+
|
|
28
|
+
| Problem | Why | How to prevent |
|
|
29
|
+
|---------|-----|----------------|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Lead
|
|
2
|
+
|
|
3
|
+
## Config
|
|
4
|
+
|
|
5
|
+
| Key | Value | Description |
|
|
6
|
+
|----------------|--------|----------------------------------------------|
|
|
7
|
+
| default_branch | 0.0.x | Default branch for CI triggers and diff base |
|
|
8
|
+
|
|
9
|
+
## Architecture & Decisions
|
|
10
|
+
- **setuptools-scm for dynamic versioning** — versions derived from git tags, agents must not hardcode versions in pyproject.toml
|
|
11
|
+
- **Branch `0.0.x` as default** — non-standard naming, affects CI triggers and diff bases
|
|
12
|
+
- **Template-based project bootstrap** — project initialized from qarium template with `${ROLE_*}` placeholders processed during employee onboarding; agents must not re-fill these values
|
|
13
|
+
|
|
14
|
+
## Project Structure
|
|
15
|
+
- **Single flat package layout (`qools/`)** — library without subdirectories, all modules at the top level
|
|
16
|
+
- **`funcutils.py` — functional utilities module** — decorators (`called_once`), constants; new decorators and helpers go here
|
|
17
|
+
|
|
18
|
+
## Code Patterns
|
|
19
|
+
- **Thread-safe singleton decorator** — `called_once` uses `threading.Lock` for safe concurrent lazy initialization
|
|
20
|
+
- **Submodule-style imports** — `from qools import funcutils` then `funcutils.called_once(...)`; `__init__.py` stays empty, no re-exports
|
|
21
|
+
- **`typing as t` convention** — all type hints use `t.` prefix instead of full paths
|
|
22
|
+
|
|
23
|
+
## TODO
|
|
24
|
+
- Fix `WrappedType` annotation in `funcutils.py:8` — replace `t.Callable[[...], t.Any]` with `t.Callable[..., t.Any]`, remove `# type: ignore[misc]`
|
|
25
|
+
- Fix exception-safety in `called_once` (`funcutils.py:21-23`) — move `is_called = True` after `rv = f(...)` so failed calls can be retried
|
|
26
|
+
- Remove unused constants `DEFAULT_TIMEOUT` and `DEFAULT_DELAY` from `funcutils.py:5-6`
|
|
27
|
+
- Add tests for `funcutils.py` in `tests/qools/test_funcutils.py`
|
|
28
|
+
|
|
29
|
+
## LLM Directives
|
|
30
|
+
<!-- empty -->
|
|
31
|
+
|
|
32
|
+
## Lessons
|
|
33
|
+
|
|
34
|
+
| Problem | Why | How to prevent |
|
|
35
|
+
|---------|-----|----------------|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
## Config
|
|
2
|
+
|
|
3
|
+
| Setting | Value |
|
|
4
|
+
|------------------|----------------------------------------|
|
|
5
|
+
| run_tests_cmd | pytest --tb=short |
|
|
6
|
+
| lint_cmd | ruff check qools/ tests/ |
|
|
7
|
+
| lint_fix_cmd | ruff check --fix qools/ tests/ |
|
|
8
|
+
| format_cmd | ruff format --check qools/ tests/ |
|
|
9
|
+
| format_fix_cmd | ruff format qools/ tests/ |
|
|
10
|
+
|
|
11
|
+
## Rules
|
|
12
|
+
|
|
13
|
+
Project test configuration. Used by the `qarium:employees:qa:feature` skill.
|
|
14
|
+
|
|
15
|
+
### Mapping
|
|
16
|
+
|
|
17
|
+
| Source path pattern | Test directory | Notes |
|
|
18
|
+
|---------------------|--------------------|---------------|
|
|
19
|
+
| `qools/**/*.py` | `tests/qools/` | Mirror layout |
|
|
20
|
+
|
|
21
|
+
### Mock Patterns
|
|
22
|
+
|
|
23
|
+
| Pattern | Example |
|
|
24
|
+
|---------|---------|
|
|
25
|
+
|
|
26
|
+
### Helpers
|
|
27
|
+
|
|
28
|
+
| Helper | Location | Purpose |
|
|
29
|
+
|--------|----------|---------|
|
|
30
|
+
|
|
31
|
+
### Conventions
|
|
32
|
+
|
|
33
|
+
- Naming: `test_<what>_<scenario>`
|
|
34
|
+
- Never mock `builtins.open` — use `tmp_path` fixture
|
|
35
|
+
- Integration tests use `pytest.mark.skipif` when external tools unavailable
|
|
36
|
+
|
|
37
|
+
## Lessons
|
|
38
|
+
|
|
39
|
+
| Problem | Why | How to prevent |
|
|
40
|
+
|---------|-----|----------------|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Tech Writer Config
|
|
2
|
+
|
|
3
|
+
## Config
|
|
4
|
+
|
|
5
|
+
| Key | Value | Description |
|
|
6
|
+
|---------------|----------------------------|-------------------------------------|
|
|
7
|
+
| build_cmd | `mkdocs build` | Build validation command |
|
|
8
|
+
| deploy_cmd | `mkdocs gh-deploy --force` | Deploy command |
|
|
9
|
+
| examples_file | | File for usage examples (optional) |
|
|
10
|
+
| logo_url | `https://avatars.githubusercontent.com/u/262344922?s=200&v=4` | Standard qarium logo |
|
|
11
|
+
| base_branch | `0.0.x` | Base branch for git diff comparison |
|
|
12
|
+
|
|
13
|
+
## Rules
|
|
14
|
+
|
|
15
|
+
### Mapping
|
|
16
|
+
|
|
17
|
+
| Source path | Documentation files |
|
|
18
|
+
|-------------|---------------------|
|
|
19
|
+
| `qools/__init__.py` | `docs/api-reference.md`, `docs/index.md` |
|
|
20
|
+
| `qools/*.py` | `docs/api-reference.md` |
|
|
21
|
+
|
|
22
|
+
### Conventions
|
|
23
|
+
|
|
24
|
+
## Lessons
|
|
25
|
+
|
|
26
|
+
| Problem | Why | How to prevent |
|
|
27
|
+
|---------|-----|----------------|
|
qools-0.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qools
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Utility tools from Qarium
|
|
5
|
+
License: MIT
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Provides-Extra: test
|
|
17
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
18
|
+
Requires-Dist: pytest-cov>=5.0; extra == "test"
|
|
19
|
+
Requires-Dist: ruff>=0.15.0; extra == "test"
|
|
20
|
+
Provides-Extra: docs
|
|
21
|
+
Requires-Dist: mkdocs>=1.6.0; extra == "docs"
|
|
22
|
+
Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
|
|
23
|
+
|
|
24
|
+
# qools
|
|
25
|
+
|
|
26
|
+
Utility tools from Qarium
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install qools
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import qools
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Documentation
|
|
41
|
+
|
|
42
|
+
Full documentation: https://qarium.github.io/qools/
|
qools-0.0.0/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# API Reference
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Getting Started
|
qools-0.0.0/mkdocs.yml
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
docs_dir: docs
|
|
2
|
+
|
|
3
|
+
site_name: qools
|
|
4
|
+
site_description: Utility tools from Qarium
|
|
5
|
+
site_url: https://qarium.github.io/qools/
|
|
6
|
+
repo_url: https://github.com/qarium/qools
|
|
7
|
+
edit_uri: edit/0.0.x/docs/
|
|
8
|
+
|
|
9
|
+
theme:
|
|
10
|
+
name: material
|
|
11
|
+
custom_dir: docs/overrides
|
|
12
|
+
logo: https://avatars.githubusercontent.com/u/262344922?s=200&v=4
|
|
13
|
+
palette:
|
|
14
|
+
- scheme: default
|
|
15
|
+
primary: custom
|
|
16
|
+
accent: indigo
|
|
17
|
+
toggle:
|
|
18
|
+
icon: material/brightness-7
|
|
19
|
+
name: Switch to dark mode
|
|
20
|
+
media: "(prefers-color-scheme: light)"
|
|
21
|
+
- scheme: slate
|
|
22
|
+
primary: custom
|
|
23
|
+
accent: indigo
|
|
24
|
+
toggle:
|
|
25
|
+
icon: material/brightness-4
|
|
26
|
+
name: Switch to light mode
|
|
27
|
+
media: "(prefers-color-scheme: dark)"
|
|
28
|
+
features:
|
|
29
|
+
- navigation.tabs
|
|
30
|
+
- navigation.indexes
|
|
31
|
+
- search.suggest
|
|
32
|
+
- search.highlight
|
|
33
|
+
- content.code.copy
|
|
34
|
+
|
|
35
|
+
nav:
|
|
36
|
+
- Home: index.md
|
|
37
|
+
- Getting Started: getting-started.md
|
|
38
|
+
- API Reference: api-reference.md
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "setuptools-scm>=8.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "qools"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Utility tools from Qarium"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
test = [
|
|
27
|
+
"pytest>=8.0",
|
|
28
|
+
"pytest-cov>=5.0",
|
|
29
|
+
"ruff>=0.15.0",
|
|
30
|
+
]
|
|
31
|
+
docs = [
|
|
32
|
+
"mkdocs>=1.6.0",
|
|
33
|
+
"mkdocs-material>=9.5.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[tool.setuptools_scm]
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.packages.find]
|
|
39
|
+
include = ["qools*"]
|
|
40
|
+
|
|
41
|
+
[tool.ruff]
|
|
42
|
+
target-version = "py310"
|
|
43
|
+
line-length = 120
|
|
44
|
+
src = ["qools"]
|
|
45
|
+
|
|
46
|
+
[tool.ruff.lint]
|
|
47
|
+
select = [
|
|
48
|
+
"E", # pycodestyle errors
|
|
49
|
+
"W", # pycodestyle warnings
|
|
50
|
+
"F", # pyflakes
|
|
51
|
+
"I", # isort
|
|
52
|
+
"UP", # pyupgrade
|
|
53
|
+
"B", # flake8-bugbear
|
|
54
|
+
"SIM", # flake8-simplify
|
|
55
|
+
"C4", # flake8-comprehensions
|
|
56
|
+
"DTZ", # flake8-datetimez
|
|
57
|
+
"PT", # flake8-pytest-style
|
|
58
|
+
]
|
|
59
|
+
ignore = [
|
|
60
|
+
"UP045", # `X | None` — not compatible with Python 3.10 dataclass fields, use `typing.Optional`
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint.per-file-ignores]
|
|
64
|
+
"tests/**/*.py" = [
|
|
65
|
+
"S101", # assert allowed in tests
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[tool.ruff.format]
|
|
69
|
+
quote-style = "double"
|
|
70
|
+
indent-style = "space"
|
|
71
|
+
|
|
72
|
+
[tool.pytest.ini_options]
|
|
73
|
+
testpaths = ["tests"]
|
|
74
|
+
addopts = "-v --tb=short"
|
|
75
|
+
|
|
76
|
+
[tool.coverage.run]
|
|
77
|
+
source = ["qools"]
|
|
78
|
+
branch = true
|
|
79
|
+
|
|
80
|
+
[tool.coverage.report]
|
|
81
|
+
show_missing = true
|
|
82
|
+
skip_empty = true
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from threading import Lock
|
|
4
|
+
|
|
5
|
+
DEFAULT_TIMEOUT: t.Final = 5
|
|
6
|
+
DEFAULT_DELAY: t.Final = 0.5
|
|
7
|
+
|
|
8
|
+
WrappedType = t.Callable[[...], t.Any] # type: ignore[misc]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def called_once(f: WrappedType) -> t.Callable:
|
|
12
|
+
rv = None
|
|
13
|
+
lock = Lock()
|
|
14
|
+
is_called = False
|
|
15
|
+
|
|
16
|
+
@wraps(f)
|
|
17
|
+
def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
|
|
18
|
+
with lock:
|
|
19
|
+
nonlocal rv, is_called
|
|
20
|
+
|
|
21
|
+
if not is_called:
|
|
22
|
+
is_called = True
|
|
23
|
+
rv = f(*args, **kwargs)
|
|
24
|
+
|
|
25
|
+
return rv
|
|
26
|
+
|
|
27
|
+
return wrapper
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qools
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Utility tools from Qarium
|
|
5
|
+
License: MIT
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Provides-Extra: test
|
|
17
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
18
|
+
Requires-Dist: pytest-cov>=5.0; extra == "test"
|
|
19
|
+
Requires-Dist: ruff>=0.15.0; extra == "test"
|
|
20
|
+
Provides-Extra: docs
|
|
21
|
+
Requires-Dist: mkdocs>=1.6.0; extra == "docs"
|
|
22
|
+
Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
|
|
23
|
+
|
|
24
|
+
# qools
|
|
25
|
+
|
|
26
|
+
Utility tools from Qarium
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install qools
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import qools
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Documentation
|
|
41
|
+
|
|
42
|
+
Full documentation: https://qarium.github.io/qools/
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
.gitignore
|
|
2
|
+
.strictacode.yml
|
|
3
|
+
README.md
|
|
4
|
+
mkdocs.yml
|
|
5
|
+
pyproject.toml
|
|
6
|
+
.github/workflows/docs.yml
|
|
7
|
+
.github/workflows/lint.yml
|
|
8
|
+
.github/workflows/new_version.yml
|
|
9
|
+
.github/workflows/publish.yml
|
|
10
|
+
.github/workflows/strictacode.yml
|
|
11
|
+
.github/workflows/tests.yml
|
|
12
|
+
.qarium/ai/employees/developer.md
|
|
13
|
+
.qarium/ai/employees/devops.md
|
|
14
|
+
.qarium/ai/employees/lead.md
|
|
15
|
+
.qarium/ai/employees/qa.md
|
|
16
|
+
.qarium/ai/employees/tech-writer.md
|
|
17
|
+
docs/api-reference.md
|
|
18
|
+
docs/getting-started.md
|
|
19
|
+
docs/index.md
|
|
20
|
+
docs/overrides/main.html
|
|
21
|
+
qools/__init__.py
|
|
22
|
+
qools/funcutils.py
|
|
23
|
+
qools.egg-info/PKG-INFO
|
|
24
|
+
qools.egg-info/SOURCES.txt
|
|
25
|
+
qools.egg-info/dependency_links.txt
|
|
26
|
+
qools.egg-info/requires.txt
|
|
27
|
+
qools.egg-info/top_level.txt
|
|
28
|
+
tests/__init__.py
|
|
29
|
+
tests/conftest.py
|
|
30
|
+
tests/test_import.py
|
|
31
|
+
tests/qools/__init__.py
|
|
32
|
+
tests/qools/test_funcutils.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
qools
|
qools-0.0.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# fixtures will be added by qarium:employees:qa:feature as needed
|
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
from threading import Thread
|
|
3
|
+
|
|
4
|
+
from qools.funcutils import DEFAULT_DELAY, DEFAULT_TIMEOUT, called_once
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestCalledOnce:
|
|
8
|
+
def test_called_once_returns_cached_result(self):
|
|
9
|
+
call_count = 0
|
|
10
|
+
|
|
11
|
+
@called_once
|
|
12
|
+
def inc() -> int:
|
|
13
|
+
nonlocal call_count
|
|
14
|
+
call_count += 1
|
|
15
|
+
return call_count
|
|
16
|
+
|
|
17
|
+
assert inc() == 1
|
|
18
|
+
assert inc() == 1
|
|
19
|
+
|
|
20
|
+
def test_called_once_calls_function_once(self):
|
|
21
|
+
call_count = 0
|
|
22
|
+
|
|
23
|
+
@called_once
|
|
24
|
+
def side_effect() -> None:
|
|
25
|
+
nonlocal call_count
|
|
26
|
+
call_count += 1
|
|
27
|
+
|
|
28
|
+
side_effect()
|
|
29
|
+
side_effect()
|
|
30
|
+
side_effect()
|
|
31
|
+
|
|
32
|
+
assert call_count == 1
|
|
33
|
+
|
|
34
|
+
def test_called_once_with_args_and_kwargs(self):
|
|
35
|
+
@called_once
|
|
36
|
+
def add(a: int, b: int, *, offset: int = 0) -> int:
|
|
37
|
+
return a + b + offset
|
|
38
|
+
|
|
39
|
+
assert add(1, 2, offset=10) == 13
|
|
40
|
+
assert add(99, 99) == 13
|
|
41
|
+
|
|
42
|
+
def test_called_once_preserves_metadata(self):
|
|
43
|
+
@called_once
|
|
44
|
+
def documented_func() -> None:
|
|
45
|
+
"""Important docstring."""
|
|
46
|
+
|
|
47
|
+
assert documented_func.__name__ == "documented_func"
|
|
48
|
+
assert documented_func.__doc__ == "Important docstring."
|
|
49
|
+
|
|
50
|
+
def test_called_once_with_none_return(self):
|
|
51
|
+
@called_once
|
|
52
|
+
def returns_none() -> None:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
assert returns_none() is None
|
|
56
|
+
assert returns_none() is None
|
|
57
|
+
|
|
58
|
+
def test_called_once_first_exception_returns_none(self):
|
|
59
|
+
@called_once
|
|
60
|
+
def raises() -> t.NoReturn:
|
|
61
|
+
raise ValueError("boom")
|
|
62
|
+
|
|
63
|
+
import pytest
|
|
64
|
+
|
|
65
|
+
with pytest.raises(ValueError, match="boom"):
|
|
66
|
+
raises()
|
|
67
|
+
assert raises() is None
|
|
68
|
+
|
|
69
|
+
def test_called_once_thread_safety(self):
|
|
70
|
+
call_count = 0
|
|
71
|
+
|
|
72
|
+
@called_once
|
|
73
|
+
def slow_inc() -> int:
|
|
74
|
+
nonlocal call_count
|
|
75
|
+
call_count += 1
|
|
76
|
+
return call_count
|
|
77
|
+
|
|
78
|
+
results: list[t.Any] = []
|
|
79
|
+
|
|
80
|
+
def worker() -> None:
|
|
81
|
+
results.append(slow_inc())
|
|
82
|
+
|
|
83
|
+
threads = [Thread(target=worker) for _ in range(10)]
|
|
84
|
+
for th in threads:
|
|
85
|
+
th.start()
|
|
86
|
+
for th in threads:
|
|
87
|
+
th.join()
|
|
88
|
+
|
|
89
|
+
assert call_count == 1
|
|
90
|
+
assert all(r == 1 for r in results)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class TestConstants:
|
|
94
|
+
def test_default_timeout_value(self):
|
|
95
|
+
assert DEFAULT_TIMEOUT == 5
|
|
96
|
+
|
|
97
|
+
def test_default_delay_value(self):
|
|
98
|
+
assert DEFAULT_DELAY == 0.5
|