├── .github
├── .DS_Store
└── workflows
│ ├── deploy-docs.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .vscode
└── launch.json
├── Makefile
├── README.md
├── docs
├── .DS_Store
├── .gitignore
├── .vitepress
│ ├── cache
│ │ └── deps
│ │ │ ├── @vueuse_core.js
│ │ │ ├── @vueuse_core.js.map
│ │ │ ├── _metadata.json
│ │ │ ├── chunk-OX6HOUGK.js
│ │ │ ├── chunk-OX6HOUGK.js.map
│ │ │ ├── package.json
│ │ │ ├── vue.js
│ │ │ └── vue.js.map
│ ├── components.d.ts
│ ├── components
│ │ ├── CopyImport.vue
│ │ ├── CourseLink.vue
│ │ ├── FeaturesList.vue
│ │ ├── HomePage.vue
│ │ ├── ListItem.vue
│ │ ├── ModuleInfo.vue
│ │ ├── ProjectInfo.vue
│ │ ├── TagPage.vue
│ │ └── Tags.vue
│ ├── composables
│ │ ├── index.ts
│ │ ├── useArticle.ts
│ │ └── useUrlParams.ts
│ ├── config.ts
│ ├── meta.ts
│ ├── scripts
│ │ ├── fetch-avatars.ts
│ │ ├── pwa.ts
│ │ └── transformHead.ts
│ ├── style
│ │ ├── main.css
│ │ └── vars.css
│ └── theme
│ │ ├── index.ts
│ │ └── pwa.ts
├── CNAME
├── api
│ ├── converter
│ │ ├── to_bool.md
│ │ ├── to_list.md
│ │ ├── to_md5.md
│ │ ├── to_set.md
│ │ └── to_string.md
│ ├── date
│ │ ├── format.md
│ │ ├── now.md
│ │ ├── parse.md
│ │ └── timestamp.md
│ ├── decorator
│ │ ├── catch_error.md
│ │ ├── retry.md
│ │ ├── singleton.md
│ │ └── throttle.md
│ ├── dict
│ │ ├── ad_dict.md
│ │ ├── merge_dicts.md
│ │ ├── sort_by_key.md
│ │ └── sort_by_value.md
│ ├── index.md
│ ├── list
│ │ ├── chunk.md
│ │ ├── compact.md
│ │ ├── count_by.md
│ │ ├── difference.md
│ │ ├── every.md
│ │ ├── flatten.md
│ │ ├── flatten_deep.md
│ │ ├── key_by.md
│ │ ├── sample.md
│ │ ├── shuffle.md
│ │ ├── some.md
│ │ ├── union.md
│ │ ├── uniq.md
│ │ ├── without.md
│ │ ├── zip_dict.md
│ │ └── zip_tuple.md
│ ├── misc
│ │ ├── dynamic_import.md
│ │ └── get_function_name.md
│ ├── string
│ │ ├── camel_case.md
│ │ ├── capitalize.md
│ │ ├── kebab_case.md
│ │ ├── left.md
│ │ ├── lower_case.md
│ │ ├── middle.md
│ │ ├── middle_batch.md
│ │ ├── pascal_case.md
│ │ ├── right.md
│ │ └── snake_case.md
│ ├── useSnowflakeId.md
│ └── validator
│ │ ├── is_async_function.md
│ │ └── is_url.md
├── ecology
│ ├── index.md
│ ├── logger.md
│ └── notify.md
├── extension
│ ├── index.md
│ ├── logger.md
│ ├── notify.md
│ ├── rabbitmq.md
│ └── redis.md
├── guide
│ ├── .DS_Store
│ ├── features.md
│ └── index.md
├── index.md
├── package.json
├── pnpm-lock.yaml
├── public
│ ├── CNAME
│ ├── _headers
│ ├── apple-touch-icon.png
│ ├── favicon.ico
│ ├── logo-shadow.svg
│ ├── logo.png
│ ├── netlify.svg
│ ├── og-original.png
│ ├── og.png
│ ├── pwa-192x192.png
│ ├── pwa-512x512.png
│ └── robots.txt
├── tags.md
├── tsconfig.json
├── utils
│ ├── bloom_filter.md
│ ├── is.md
│ ├── timeout.md
│ ├── timer.md
│ ├── to.md
│ └── utils.md
└── vite.config.ts
├── example
└── example.py
├── logo-shadow.svg
├── pyproject.toml
├── resources
└── overview.png
├── src
└── usepy
│ ├── __init__.py
│ ├── converter
│ ├── __init__.py
│ ├── to_bool.py
│ ├── to_list.py
│ ├── to_md5.py
│ ├── to_set.py
│ └── to_string.py
│ ├── date
│ ├── __init__.py
│ ├── format.py
│ ├── now.py
│ ├── parse.py
│ └── timestamp.py
│ ├── decorator
│ ├── __init__.py
│ ├── catch_error.py
│ ├── retry.py
│ ├── singleton.py
│ └── throttle.py
│ ├── dict
│ ├── __init__.py
│ ├── ad_dict.py
│ ├── merge_dicts.py
│ ├── sort_by_key.py
│ └── sort_by_value.py
│ ├── list
│ ├── __init__.py
│ ├── chunk.py
│ ├── compact.py
│ ├── count_by.py
│ ├── difference.py
│ ├── every.py
│ ├── first.py
│ ├── flatten.py
│ ├── flatten_deep.py
│ ├── key_by.py
│ ├── last.py
│ ├── sample.py
│ ├── shuffle.py
│ ├── some.py
│ ├── union.py
│ ├── uniq.py
│ ├── without.py
│ ├── zip_dict.py
│ └── zip_tuple.py
│ ├── misc
│ ├── __init__.py
│ ├── dynamic_import.py
│ └── get_function_name.py
│ ├── string
│ ├── __init__.py
│ ├── _get_section.py
│ ├── _get_words.py
│ ├── camel_case.py
│ ├── capitalize.py
│ ├── kebab_case.py
│ ├── left.py
│ ├── lower_case.py
│ ├── middle.py
│ ├── middle_batch.py
│ ├── pascal_case.py
│ ├── right.py
│ └── snake_case.py
│ └── validator
│ ├── __init__.py
│ ├── is_async_function.py
│ └── is_url.py
└── tests
├── test_addict.py
├── test_converter.py
├── test_date.py
├── test_decorator.py
├── test_dict.py
├── test_list.py
├── test_misc.py
├── test_string.py
└── test_validator.py
/.github/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/use-py/usepy/44e2436da3d95b3185a7669daa0af27674326a64/.github/.DS_Store
--------------------------------------------------------------------------------
/.github/workflows/deploy-docs.yml:
--------------------------------------------------------------------------------
1 | name: docs
2 |
3 | on:
4 | push:
5 | branches:
6 | - docs
7 | paths:
8 | - 'docs/**'
9 | - '.github/workflows/deploy-docs.yml'
10 | pull_request:
11 | branches:
12 | - main
13 | paths:
14 | - 'docs/**'
15 | - '.github/workflows/deploy-docs.yml'
16 |
17 | jobs:
18 | docs:
19 | runs-on: ubuntu-latest
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 | with:
24 | # “最近更新时间” 等 git 日志相关信息,需要拉取全部提交记录
25 | fetch-depth: 0
26 |
27 | - name: Setup Node.js
28 | uses: actions/setup-node@v1
29 | with:
30 | # 选择要使用的 node 版本
31 | node-version: '18.8.0'
32 | - name: Install pnpm
33 | uses: pnpm/action-setup@v2
34 | with:
35 | version: 7.17.0
36 | # 如果缓存没有命中,安装依赖
37 | - name: Install dependencies
38 | run: pnpm install --dir docs
39 |
40 | # 运行构建脚本
41 | - name: Build VitePress site
42 | run: cd docs && pnpm run build-no-prefetch
43 |
44 | # 查看 workflow 的文档来获取更多信息
45 | # @see https://github.com/crazy-max/ghaction-github-pages
46 | - name: Deploy to GitHub Pages
47 | uses: crazy-max/ghaction-github-pages@v2
48 | with:
49 | # 部署到 gh-pages 分支
50 | target_branch: gh-pages
51 | # 部署目录为 VuePress 的默认输出目录
52 | build_dir: ./docs/.vitepress/dist
53 | env:
54 | # @see https://docs.github.com/cn/actions/reference/authentication-in-a-workflow#about-the-github_token-secret
55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
56 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*.*.*'
7 |
8 | jobs:
9 | release:
10 | name: Release
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v3
15 |
16 | - name: Set up Python 3.10
17 | uses: actions/setup-python@v4
18 | with:
19 | python-version: "3.10"
20 |
21 | - name: Install Poetry
22 | uses: snok/install-poetry@v1
23 | with:
24 | virtualenvs-create: true
25 | virtualenvs-in-project: true
26 | installer-parallel: true
27 |
28 | - name: Build project for distribution
29 | run: poetry build
30 |
31 | - name: Check Version
32 | id: check-version
33 | run: |
34 | [[ "$(poetry version --short)" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] \
35 | || echo ::set-output name=prerelease::true
36 |
37 | - name: Create Release
38 | uses: ncipollo/release-action@v1
39 | with:
40 | artifacts: "dist/*"
41 | token: ${{ secrets.GITHUB_TOKEN }}
42 | draft: false
43 | prerelease: steps.check-version.outputs.prerelease == 'true'
44 | allowUpdates: true
45 |
46 | - name: Publish to PyPI
47 | env:
48 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }}
49 | run: poetry publish
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test suite
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 |
8 | jobs:
9 | test:
10 | strategy:
11 | fail-fast: true
12 | matrix:
13 | os: [ "ubuntu-latest" ]
14 | python-version: [ "3.7", "3.8", "3.9", "3.10" ]
15 | runs-on: ${{ matrix.os }}
16 | steps:
17 | - uses: actions/checkout@v2
18 | - name: Set up Python ${{ matrix.python-version }}
19 | uses: actions/setup-python@v2
20 | with:
21 | python-version: ${{ matrix.python-version }}
22 | - name: Install Poetry
23 | uses: snok/install-poetry@v1
24 | with:
25 | version: 1.5.1
26 | virtualenvs-create: true
27 | virtualenvs-in-project: true
28 | installer-parallel: true
29 | - name: Load cached venv
30 | id: cached-poetry-dependencies
31 | uses: actions/cache@v2
32 | with:
33 | path: .venv
34 | key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
35 | - name: Install dependencies
36 | if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
37 | run: poetry install --no-interaction --no-root
38 | - name: Install library
39 | run: poetry install --no-interaction
40 | - name: Run tests
41 | run: |
42 | source .venv/bin/activate
43 | pytest tests/
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # created by virtualenv automatically
2 | # Byte-compiled / optimized / DLL files
3 | __pycache__/
4 | *.py[cod]
5 | *$py.class
6 |
7 | # C extensions
8 | *.so
9 |
10 | # Distribution / packaging
11 | .Python
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | pip-wheel-metadata/
25 | share/python-wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 | MANIFEST
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .nox/
45 | .coverage
46 | .coverage.*
47 | .cache
48 | nosetests.xml
49 | coverage.xml
50 | *.cover
51 | *.py,cover
52 | .hypothesis/
53 | .pytest_cache/
54 |
55 | # Translations
56 | *.mo
57 | *.pot
58 |
59 | # Django stuff:
60 | *.log
61 | local_settings.py
62 | db.sqlite3
63 | db.sqlite3-journal
64 |
65 | # Flask stuff:
66 | instance/
67 | .webassets-cache
68 |
69 | # Scrapy stuff:
70 | .scrapy
71 |
72 | # Sphinx documentation
73 | docs/_build/
74 |
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 | .python-version
87 |
88 | # pipenv
89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
92 | # install all needed dependencies.
93 | #Pipfile.lock
94 |
95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
96 | __pypackages__/
97 |
98 | # Celery stuff
99 | celerybeat-schedule
100 | celerybeat.pid
101 |
102 | # SageMath parsed files
103 | *.sage.py
104 |
105 | # Environments
106 | .env
107 | .venv
108 | env/
109 | venv/
110 | ENV/
111 | env.bak/
112 | venv.bak/
113 |
114 | # Spyder project settings
115 | .spyderproject
116 | .spyproject
117 |
118 | # Rope project settings
119 | .ropeproject
120 |
121 | # mkdocs documentation
122 | /site
123 |
124 | # mypy
125 | .mypy_cache/
126 | .dmypy.json
127 | dmypy.json
128 |
129 | # Pyre type checker
130 | .pyre/
131 |
132 | # pycharm
133 | .idea
134 | poetry.lock
135 | *.DS_Store
136 |
137 | # docs/node_modules
138 | docs/node_modules
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Python Debugger: Current File",
9 | "type": "debugpy",
10 | "request": "launch",
11 | "program": "${file}",
12 | "console": "integratedTerminal",
13 | "env": {
14 | "PYTHONPATH": "${workspaceFolder}/src"
15 | }
16 | },
17 | {
18 | "name": "PyTest: Current File",
19 | "type": "debugpy",
20 | "request": "launch",
21 | "module": "pytest",
22 | "args": [
23 | "${file}"
24 | ],
25 | "console": "integratedTerminal",
26 | "env": {
27 | "PYTHONPATH": "${workspaceFolder}/src"
28 | }
29 | },
30 | {
31 | "name": "PyTest",
32 | "type": "debugpy",
33 | "request": "launch",
34 | "module": "pytest",
35 | "args": [
36 | "tests"
37 | ],
38 | "console": "integratedTerminal",
39 | "env": {
40 | "PYTHONPATH": "${workspaceFolder}/src"
41 | }
42 | },
43 | {
44 | "name": "DocTest",
45 | "type": "debugpy",
46 | "request": "launch",
47 | "module": "doctest",
48 | "args": [
49 | "-v",
50 | "${file}"
51 | ]
52 | }
53 | ]
54 | }
55 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | install: ## Run `poetry install`
2 | poetry install --no-root
3 |
4 | lint:
5 | poetry run ruff .
6 | poetry run ruff check .
7 |
8 | format: ## Formasts you code with Black
9 | poetry run ruff . --fix
10 | poetry run ruff check . --fix
11 |
12 | test:
13 | poetry run pytest -v tests
14 |
15 | publish:
16 | poetry publish --build
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
19 |
20 | ### 安装
21 |
22 | ```bash
23 | pip install usepy -U
24 | ```
25 |
26 |
27 |
28 | 更多参阅[官方文档](https://usepy.code05.com/)
29 |
30 | ## 贡献
31 |
32 | 欢迎提交PR,一起完善这个工具库。
33 |
--------------------------------------------------------------------------------
/docs/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/use-py/usepy/44e2436da3d95b3185a7669daa0af27674326a64/docs/.DS_Store
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | .pnpm-store/
2 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/_metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "hash": "fb6e3b58",
3 | "browserHash": "e2a8033e",
4 | "optimized": {
5 | "vue": {
6 | "src": "../../../node_modules/.pnpm/vue@3.3.4/node_modules/vue/dist/vue.runtime.esm-bundler.js",
7 | "file": "vue.js",
8 | "fileHash": "766895cd",
9 | "needsInterop": false
10 | },
11 | "@vueuse/core": {
12 | "src": "../../../node_modules/.pnpm/@vueuse+core@9.12.0_vue@3.3.4/node_modules/@vueuse/core/index.mjs",
13 | "file": "@vueuse_core.js",
14 | "fileHash": "375b6041",
15 | "needsInterop": false
16 | }
17 | },
18 | "chunks": {
19 | "chunk-OX6HOUGK": {
20 | "file": "chunk-OX6HOUGK.js"
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "module"
3 | }
4 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/vue.js:
--------------------------------------------------------------------------------
1 | import {
2 | BaseTransition,
3 | BaseTransitionPropsValidators,
4 | Comment,
5 | EffectScope,
6 | Fragment,
7 | KeepAlive,
8 | ReactiveEffect,
9 | Static,
10 | Suspense,
11 | Teleport,
12 | Text,
13 | Transition,
14 | TransitionGroup,
15 | VueElement,
16 | assertNumber,
17 | callWithAsyncErrorHandling,
18 | callWithErrorHandling,
19 | camelize,
20 | capitalize,
21 | cloneVNode,
22 | compatUtils,
23 | compile,
24 | computed,
25 | createApp,
26 | createBaseVNode,
27 | createBlock,
28 | createCommentVNode,
29 | createElementBlock,
30 | createHydrationRenderer,
31 | createPropsRestProxy,
32 | createRenderer,
33 | createSSRApp,
34 | createSlots,
35 | createStaticVNode,
36 | createTextVNode,
37 | createVNode,
38 | customRef,
39 | defineAsyncComponent,
40 | defineComponent,
41 | defineCustomElement,
42 | defineEmits,
43 | defineExpose,
44 | defineModel,
45 | defineOptions,
46 | defineProps,
47 | defineSSRCustomElement,
48 | defineSlots,
49 | devtools,
50 | effect,
51 | effectScope,
52 | getCurrentInstance,
53 | getCurrentScope,
54 | getTransitionRawChildren,
55 | guardReactiveProps,
56 | h,
57 | handleError,
58 | hasInjectionContext,
59 | hydrate,
60 | initCustomFormatter,
61 | initDirectivesForSSR,
62 | inject,
63 | isMemoSame,
64 | isProxy,
65 | isReactive,
66 | isReadonly,
67 | isRef,
68 | isRuntimeOnly,
69 | isShallow,
70 | isVNode,
71 | markRaw,
72 | mergeDefaults,
73 | mergeModels,
74 | mergeProps,
75 | nextTick,
76 | normalizeClass,
77 | normalizeProps,
78 | normalizeStyle,
79 | onActivated,
80 | onBeforeMount,
81 | onBeforeUnmount,
82 | onBeforeUpdate,
83 | onDeactivated,
84 | onErrorCaptured,
85 | onMounted,
86 | onRenderTracked,
87 | onRenderTriggered,
88 | onScopeDispose,
89 | onServerPrefetch,
90 | onUnmounted,
91 | onUpdated,
92 | openBlock,
93 | popScopeId,
94 | provide,
95 | proxyRefs,
96 | pushScopeId,
97 | queuePostFlushCb,
98 | reactive,
99 | readonly,
100 | ref,
101 | registerRuntimeCompiler,
102 | render,
103 | renderList,
104 | renderSlot,
105 | resolveComponent,
106 | resolveDirective,
107 | resolveDynamicComponent,
108 | resolveFilter,
109 | resolveTransitionHooks,
110 | setBlockTracking,
111 | setDevtoolsHook,
112 | setTransitionHooks,
113 | shallowReactive,
114 | shallowReadonly,
115 | shallowRef,
116 | ssrContextKey,
117 | ssrUtils,
118 | stop,
119 | toDisplayString,
120 | toHandlerKey,
121 | toHandlers,
122 | toRaw,
123 | toRef,
124 | toRefs,
125 | toValue,
126 | transformVNodeArgs,
127 | triggerRef,
128 | unref,
129 | useAttrs,
130 | useCssModule,
131 | useCssVars,
132 | useModel,
133 | useSSRContext,
134 | useSlots,
135 | useTransitionState,
136 | vModelCheckbox,
137 | vModelDynamic,
138 | vModelRadio,
139 | vModelSelect,
140 | vModelText,
141 | vShow,
142 | version,
143 | warn,
144 | watch,
145 | watchEffect,
146 | watchPostEffect,
147 | watchSyncEffect,
148 | withAsyncContext,
149 | withCtx,
150 | withDefaults,
151 | withDirectives,
152 | withKeys,
153 | withMemo,
154 | withModifiers,
155 | withScopeId
156 | } from "./chunk-OX6HOUGK.js";
157 | export {
158 | BaseTransition,
159 | BaseTransitionPropsValidators,
160 | Comment,
161 | EffectScope,
162 | Fragment,
163 | KeepAlive,
164 | ReactiveEffect,
165 | Static,
166 | Suspense,
167 | Teleport,
168 | Text,
169 | Transition,
170 | TransitionGroup,
171 | VueElement,
172 | assertNumber,
173 | callWithAsyncErrorHandling,
174 | callWithErrorHandling,
175 | camelize,
176 | capitalize,
177 | cloneVNode,
178 | compatUtils,
179 | compile,
180 | computed,
181 | createApp,
182 | createBlock,
183 | createCommentVNode,
184 | createElementBlock,
185 | createBaseVNode as createElementVNode,
186 | createHydrationRenderer,
187 | createPropsRestProxy,
188 | createRenderer,
189 | createSSRApp,
190 | createSlots,
191 | createStaticVNode,
192 | createTextVNode,
193 | createVNode,
194 | customRef,
195 | defineAsyncComponent,
196 | defineComponent,
197 | defineCustomElement,
198 | defineEmits,
199 | defineExpose,
200 | defineModel,
201 | defineOptions,
202 | defineProps,
203 | defineSSRCustomElement,
204 | defineSlots,
205 | devtools,
206 | effect,
207 | effectScope,
208 | getCurrentInstance,
209 | getCurrentScope,
210 | getTransitionRawChildren,
211 | guardReactiveProps,
212 | h,
213 | handleError,
214 | hasInjectionContext,
215 | hydrate,
216 | initCustomFormatter,
217 | initDirectivesForSSR,
218 | inject,
219 | isMemoSame,
220 | isProxy,
221 | isReactive,
222 | isReadonly,
223 | isRef,
224 | isRuntimeOnly,
225 | isShallow,
226 | isVNode,
227 | markRaw,
228 | mergeDefaults,
229 | mergeModels,
230 | mergeProps,
231 | nextTick,
232 | normalizeClass,
233 | normalizeProps,
234 | normalizeStyle,
235 | onActivated,
236 | onBeforeMount,
237 | onBeforeUnmount,
238 | onBeforeUpdate,
239 | onDeactivated,
240 | onErrorCaptured,
241 | onMounted,
242 | onRenderTracked,
243 | onRenderTriggered,
244 | onScopeDispose,
245 | onServerPrefetch,
246 | onUnmounted,
247 | onUpdated,
248 | openBlock,
249 | popScopeId,
250 | provide,
251 | proxyRefs,
252 | pushScopeId,
253 | queuePostFlushCb,
254 | reactive,
255 | readonly,
256 | ref,
257 | registerRuntimeCompiler,
258 | render,
259 | renderList,
260 | renderSlot,
261 | resolveComponent,
262 | resolveDirective,
263 | resolveDynamicComponent,
264 | resolveFilter,
265 | resolveTransitionHooks,
266 | setBlockTracking,
267 | setDevtoolsHook,
268 | setTransitionHooks,
269 | shallowReactive,
270 | shallowReadonly,
271 | shallowRef,
272 | ssrContextKey,
273 | ssrUtils,
274 | stop,
275 | toDisplayString,
276 | toHandlerKey,
277 | toHandlers,
278 | toRaw,
279 | toRef,
280 | toRefs,
281 | toValue,
282 | transformVNodeArgs,
283 | triggerRef,
284 | unref,
285 | useAttrs,
286 | useCssModule,
287 | useCssVars,
288 | useModel,
289 | useSSRContext,
290 | useSlots,
291 | useTransitionState,
292 | vModelCheckbox,
293 | vModelDynamic,
294 | vModelRadio,
295 | vModelSelect,
296 | vModelText,
297 | vShow,
298 | version,
299 | warn,
300 | watch,
301 | watchEffect,
302 | watchPostEffect,
303 | watchSyncEffect,
304 | withAsyncContext,
305 | withCtx,
306 | withDefaults,
307 | withDirectives,
308 | withKeys,
309 | withMemo,
310 | withModifiers,
311 | withScopeId
312 | };
313 | //# sourceMappingURL=vue.js.map
314 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/vue.js.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "sources": [],
4 | "sourcesContent": [],
5 | "mappings": "",
6 | "names": []
7 | }
8 |
--------------------------------------------------------------------------------
/docs/.vitepress/components.d.ts:
--------------------------------------------------------------------------------
1 | // generated by unplugin-vue-components
2 | // We suggest you to commit this file into source control
3 | // Read more: https://github.com/vuejs/core/pull/3399
4 | import '@vue/runtime-core'
5 |
6 | export {}
7 |
8 | declare module '@vue/runtime-core' {
9 | export interface GlobalComponents {
10 | CopyImport: typeof import('./components/CopyImport.vue')['default']
11 | CourseLink: typeof import('./components/CourseLink.vue')['default']
12 | Demo: typeof import('./components/Demo.vue')['default']
13 | FeaturesList: typeof import('./components/FeaturesList.vue')['default']
14 | HomePage: typeof import('./components/HomePage.vue')['default']
15 | ListItem: typeof import('./components/ListItem.vue')['default']
16 | ModuleInfo: typeof import('./components/ModuleInfo.vue')['default']
17 | ProjectInfo: typeof import('./components/ProjectInfo.vue')['default']
18 | RouterLink: typeof import('vue-router')['RouterLink']
19 | RouterView: typeof import('vue-router')['RouterView']
20 | TagPage: typeof import('./components/TagPage.vue')['default']
21 | Tags: typeof import('./components/Tags.vue')['default']
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/CopyImport.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 | 复制导入
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/CourseLink.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/FeaturesList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 全面覆盖测试用例
4 | 基于Python类型注解
5 |
6 |
7 |
8 |
20 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/HomePage.vue:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | 所有贡献者
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/ListItem.vue:
--------------------------------------------------------------------------------
1 |
38 |
39 |
40 |
41 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
63 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/ModuleInfo.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 | {{ meta }}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/ProjectInfo.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
28 |
29 |
30 |
32 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/TagPage.vue:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 | 标签为
{{ tagName }} 的文章:
24 |
29 |
30 |
31 |
44 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Tags.vue:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 |
34 | Tags:
35 |
36 | {{ tag }}
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/docs/.vitepress/composables/index.ts:
--------------------------------------------------------------------------------
1 | import { useArticles } from './useArticle';
2 | import { useUrlParams } from './useUrlParams';
3 |
4 | export { useArticles, useUrlParams };
5 |
--------------------------------------------------------------------------------
/docs/.vitepress/composables/useArticle.ts:
--------------------------------------------------------------------------------
1 | import { useData } from 'vitepress';
2 |
3 | export function useArticles() {
4 | const { theme } = useData()
5 |
6 | const { sidebar } = theme.value
7 | const articles: any[] = [];
8 | Object.keys(sidebar).forEach((dir) => {
9 | for (const item of sidebar[dir]) {
10 | item.items.forEach((route: any) =>
11 | articles.push({
12 | ...route,
13 | parentLink: dir,
14 | parentText: item.text,
15 | tags: route.tags
16 | })
17 | );
18 | }
19 |
20 | });
21 | return articles
22 | }
23 |
--------------------------------------------------------------------------------
/docs/.vitepress/composables/useUrlParams.ts:
--------------------------------------------------------------------------------
1 | export function useUrlParams(url: string) {
2 | let arr = url.split('?');
3 | let params = arr[1].split('&');
4 | let obj: { [propname: string]: string } = {};
5 | for (let i = 0; i < params.length; i++) {
6 | let param = params[i].split('=');
7 | obj[param[0]] = param[1];
8 | }
9 | return obj;
10 | }
11 |
--------------------------------------------------------------------------------
/docs/.vitepress/config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitepress'
2 | import type { DefaultTheme } from 'vitepress/types/default-theme'
3 | import { withPwa } from '@vite-pwa/vitepress'
4 | import {
5 | font,
6 | github,
7 | blog,
8 | ogImage,
9 | ogUrl,
10 | vitestDescription, vitestName,
11 | } from './meta'
12 | import { pwa } from './scripts/pwa'
13 | import { transformHead } from './scripts/transformHead'
14 |
15 | interface SidebarItem extends DefaultTheme.SidebarItem {
16 | tags?: string[]
17 | }
18 |
19 | export default withPwa(defineConfig({
20 | lang: 'zh-CN',
21 | title: vitestName,
22 | description: vitestDescription,
23 | head: [
24 | ['meta', { name: 'theme-color', content: '#729b1a' }],
25 | ['link', { rel: 'icon', href: '/logo.svg', type: 'image/svg+xml' }],
26 | ['link', { rel: 'alternate icon', href: '/favicon.ico', type: 'image/png', sizes: '16x16' }],
27 | ['meta', { name: 'keywords', content: 'vitest, vite, test, coverage, snapshot, react, vue, preact, svelte, solid, lit, ruby, cypress, puppeteer, jsdom, happy-dom, test-runner, jest, typescript, esm, tinypool, tinyspy, c8, node' }],
28 | ['meta', { property: 'og:title', content: vitestName }],
29 | ['meta', { property: 'og:description', content: vitestDescription }],
30 | ['meta', { property: 'og:url', content: ogUrl }],
31 | ['meta', { property: 'og:image', content: ogImage }],
32 | ['meta', { name: 'twitter:title', content: vitestName }],
33 | ['meta', { name: 'twitter:description', content: vitestDescription }],
34 | ['meta', { name: 'twitter:image', content: ogImage }],
35 | ['meta', { name: 'twitter:card', content: 'summary_large_image' }],
36 | ['link', { rel: 'preload', as: 'style', onload: 'this.onload=null;this.rel=\'stylesheet\'', href: font }],
37 | ['noscript', {}, ``],
38 | ['link', { rel: 'mask-icon', href: '/logo.svg', color: '#ffffff' }],
39 | ['link', { rel: 'apple-touch-icon', href: '/apple-touch-icon.png', sizes: '180x180' }],
40 | ],
41 | lastUpdated: true,
42 | markdown: {
43 | theme: 'vitesse-dark',
44 | },
45 | themeConfig: {
46 | logo: '/logo.png',
47 |
48 | editLink: {
49 | pattern: 'https://github.com/mic1on/usepy/tree/main/docs/:path',
50 | text: '对此页面提出更改建议',
51 | },
52 |
53 | algolia: {
54 | appId: 'YEWHQHLU31',
55 | apiKey: 'bb61897ed96af7fc93af57f7e2106623',
56 | indexName: 'usepy-52caiji',
57 | // searchParameters: {
58 | // facetFilters: ['tags:en'],
59 | // },
60 | },
61 |
62 | socialLinks: [
63 | { icon: 'github', link: github },
64 | {
65 | icon: {
66 | svg: ''
67 | }, link: blog
68 | },
69 | ],
70 |
71 | footer: {
72 | message: 'Released under the MIT License.',
73 | copyright: 'Copyright © 2023 MicLon',
74 | },
75 |
76 | nav: [
77 | { text: '指南', link: '/guide/' },
78 | { text: 'API', link: '/api/' },
79 | { text: '生态', link: '/ecology/' },
80 | ],
81 | sidebar: {
82 | '/ecology': [
83 | // {
84 | // text: '生态',
85 | // items: [
86 | // {
87 | // text: 'usepy-logger',
88 | // link: '/plugin/logger',
89 | // },
90 | // {
91 | // text: 'usepy-plugin-notify',
92 | // link: '/plugin/notify',
93 | // }
94 | // ],
95 | // }
96 | ],
97 | '/': [
98 | {
99 | text: '指南',
100 | items: [
101 | {
102 | text: '快速开始',
103 | link: '/guide/',
104 | }
105 | ],
106 | },
107 | {
108 | text: 'List',
109 | items: [
110 | {
111 | text: 'chunk',
112 | link: '/api/list/chunk',
113 | },
114 | {
115 | text: 'compact',
116 | link: '/api/list/compact',
117 | },
118 | {
119 | text: 'count_by',
120 | link: '/api/list/count_by',
121 | },
122 | {
123 | text: 'difference',
124 | link: '/api/list/difference',
125 | },
126 | {
127 | text: 'every',
128 | link: '/api/list/every',
129 | },
130 | {
131 | text: 'flatten',
132 | link: '/api/list/flatten',
133 | },
134 | {
135 | text: 'flatten_deep',
136 | link: '/api/list/flatten_deep',
137 | },
138 | {
139 | text: 'key_by',
140 | link: '/api/list/key_by',
141 | },
142 | {
143 | text: 'sample',
144 | link: '/api/list/sample',
145 | },
146 | {
147 | text: 'shuffle',
148 | link: '/api/list/shuffle',
149 | },
150 | {
151 | text: 'some',
152 | link: '/api/list/some',
153 | },
154 | {
155 | text: 'union',
156 | link: '/api/list/union',
157 | },
158 | {
159 | text: 'uniq',
160 | link: '/api/list/uniq',
161 | },
162 | {
163 | text: 'without',
164 | link: '/api/list/without',
165 | },
166 | {
167 | text: 'zip_dict',
168 | link: '/api/list/zip_dict',
169 | },
170 | {
171 | text: 'zip_tuple',
172 | link: '/api/list/zip_tuple',
173 | },
174 | ],
175 | },
176 | {
177 | text: 'Dict',
178 | items: [
179 | {
180 | text: 'ad_dict',
181 | link: '/api/dict/ad_dict',
182 | },
183 | {
184 | text: 'merge_dicts',
185 | link: '/api/dict/merge_dicts',
186 | },
187 | {
188 | text: 'sort_by_key',
189 | link: '/api/dict/sort_by_key',
190 | },
191 | {
192 | text: 'sort_by_value',
193 | link: '/api/dict/sort_by_value',
194 | },
195 | ],
196 | },
197 | {
198 | text: 'String',
199 | items: [
200 | {
201 | text: 'capitalize',
202 | link: '/api/string/capitalize',
203 | },
204 | {
205 | text: 'camel_case',
206 | link: '/api/string/camel_case',
207 | },
208 | {
209 | text: 'kebab_case',
210 | link: '/api/string/kebab_case',
211 | },
212 | {
213 | text: 'snake_case',
214 | link: '/api/string/snake_case',
215 | },
216 | {
217 | text: 'pascal_case',
218 | link: '/api/string/pascal_case',
219 | },
220 | {
221 | text: 'lower_case',
222 | link: '/api/string/lower_case',
223 | },
224 | {
225 | text: 'left',
226 | link: '/api/string/left',
227 | },
228 | {
229 | text: 'right',
230 | link: '/api/string/right',
231 | },
232 | {
233 | text: 'middle',
234 | link: '/api/string/middle',
235 | },
236 | {
237 | text: 'middle_batch',
238 | link: '/api/string/middle_batch',
239 | },
240 | ],
241 | },
242 |
243 | {
244 | text: 'Date',
245 | items: [
246 | {
247 | text: 'parse',
248 | link: '/api/date/parse',
249 | },
250 | {
251 | text: 'format',
252 | link: '/api/date/format',
253 | },
254 | {
255 | text: 'now',
256 | link: '/api/date/now',
257 | },
258 | {
259 | text: 'timestamp',
260 | link: '/api/date/timestamp',
261 | },
262 | ],
263 | },
264 | {
265 | text: 'Decorator',
266 | items: [
267 | {
268 | text: 'catch_error',
269 | link: '/api/decorator/catch_error',
270 | },
271 | {
272 | text: 'retry',
273 | link: '/api/decorator/retry',
274 | },
275 | {
276 | text: 'singleton',
277 | link: '/api/decorator/singleton',
278 | },
279 | {
280 | text: 'throttle',
281 | link: '/api/decorator/throttle',
282 | },
283 | ],
284 | },
285 | {
286 | text: 'Converter',
287 | items: [
288 | {
289 | text: 'to_bool',
290 | link: '/api/converter/to_bool',
291 | },
292 | {
293 | text: 'to_list',
294 | link: '/api/converter/to_list',
295 | },
296 | {
297 | text: 'to_md5',
298 | link: '/api/converter/to_md5',
299 | },
300 | {
301 | text: 'to_set',
302 | link: '/api/converter/to_set',
303 | },
304 | {
305 | text: 'to_string',
306 | link: '/api/converter/to_string',
307 | },
308 | ],
309 | },
310 | {
311 | text: 'Validator',
312 | items: [
313 | {
314 | text: 'is_async_function',
315 | link: '/api/validator/is_async_function',
316 | },
317 | {
318 | text: 'is_url',
319 | link: '/api/validator/is_url',
320 | },
321 | ],
322 | },
323 | {
324 | text: 'Misc',
325 | items: [
326 | {
327 | text: 'dynamic_import',
328 | link: '/api/misc/dynamic_import',
329 | },
330 | {
331 | text: 'get_function_name',
332 | link: '/api/misc/get_function_name',
333 | },
334 | ],
335 | },
336 | ] as SidebarItem[],
337 | },
338 | },
339 | pwa,
340 | transformHead,
341 | }))
342 |
--------------------------------------------------------------------------------
/docs/.vitepress/meta.ts:
--------------------------------------------------------------------------------
1 | // noinspection ES6PreferShortImport: IntelliJ IDE hint to avoid warning to use `~/contributors`, will fail on build if changed
2 |
3 | /* Texts */
4 | export const vitestName = 'UsePy'
5 | export const vitestShortName = 'UsePy'
6 | export const vitestDescription = '简单又便捷的Python工具包'
7 |
8 | /* CDN fonts and styles */
9 | export const googleapis = 'https://fonts.googleapis.com'
10 | export const gstatic = 'https://fonts.gstatic.com'
11 | export const font = `${googleapis}/css2?family=Readex+Pro:wght@200;400;600&display=swap`
12 |
13 | /* vitepress head */
14 | export const ogUrl = 'https://usepy.code05.com/'
15 | export const ogImage = `${ogUrl}og.png`
16 |
17 | /* GitHub and social links */
18 | export const github = 'https://github.com/mic1on/usepy'
19 | export const blog = 'https://code05.com'
20 | export const releases = 'https://github.com/mic1on/usepy/releases'
21 |
22 | /* Avatar/Image/Sponsors servers */
23 | export const preconnectLinks = [googleapis, gstatic]
24 | export const preconnectHomeLinks = [googleapis, gstatic]
25 |
26 | /* PWA runtime caching urlPattern regular expressions */
27 | export const pwaFontsRegex = new RegExp(`^${googleapis}/.*`, 'i')
28 | export const pwaFontStylesRegex = new RegExp(`^${gstatic}/.*`, 'i')
29 | // eslint-disable-next-line prefer-regex-literals
30 | export const githubusercontentRegex = new RegExp('^https://((i.ibb.co)|((raw|user-images).githubusercontent.com))/.*', 'i')
31 |
--------------------------------------------------------------------------------
/docs/.vitepress/scripts/fetch-avatars.ts:
--------------------------------------------------------------------------------
1 | import { join, resolve } from 'pathe'
2 | import fs from 'fs-extra'
3 | import { $fetch } from 'ohmyfetch'
4 |
5 | const docsDir = resolve(__dirname, '../..')
6 | const pathContributors = resolve(docsDir, '.vitepress/contributor-names.json')
7 | const dirAvatars = resolve(docsDir, 'public/user-avatars/')
8 | const dirSponsors = resolve(docsDir, 'public/sponsors/')
9 |
10 | let contributors: string[] = []
11 |
12 | async function download(url: string, fileName: string) {
13 | if (fs.existsSync(fileName))
14 | return
15 | // eslint-disable-next-line no-console
16 | console.log('downloading', fileName)
17 | try {
18 | const image = await $fetch(url, { responseType: 'arrayBuffer' })
19 | await fs.writeFile(fileName, Buffer.from(image))
20 | }
21 | catch {}
22 | }
23 |
24 | async function fetchAvatars() {
25 | await fs.ensureDir(dirAvatars)
26 | contributors = JSON.parse(await fs.readFile(pathContributors, { encoding: 'utf-8' }))
27 |
28 | await Promise.all(contributors.map(name => download(`https://github.com/${name}.png?size=100`, join(dirAvatars, `${name}.png`))))
29 | }
30 |
31 | async function fetchSponsors() {
32 | await fs.ensureDir(dirSponsors)
33 | await download('https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg', join(dirSponsors, 'antfu.svg'))
34 | await download('https://cdn.jsdelivr.net/gh/patak-dev/static/sponsors.svg', join(dirSponsors, 'patak-dev.svg'))
35 | await download('https://cdn.jsdelivr.net/gh/sheremet-va/static/sponsors.svg', join(dirSponsors, 'sheremet-va.svg'))
36 | }
37 |
38 | fetchAvatars()
39 | fetchSponsors()
40 |
--------------------------------------------------------------------------------
/docs/.vitepress/scripts/pwa.ts:
--------------------------------------------------------------------------------
1 | import type { VitePWAOptions } from 'vite-plugin-pwa'
2 | import {
3 | githubusercontentRegex,
4 | pwaFontStylesRegex,
5 | pwaFontsRegex,
6 | vitestDescription,
7 | vitestName,
8 | vitestShortName,
9 | } from '../meta'
10 |
11 | export const pwa: Partial = {
12 | outDir: '.vitepress/dist',
13 | registerType: 'autoUpdate',
14 | // include all static assets under public/
15 | manifest: {
16 | id: '/',
17 | name: vitestName,
18 | short_name: vitestShortName,
19 | description: vitestDescription,
20 | theme_color: '#ffffff',
21 | icons: [
22 | {
23 | src: 'pwa-192x192.png',
24 | sizes: '192x192',
25 | type: 'image/png',
26 | },
27 | {
28 | src: 'pwa-512x512.png',
29 | sizes: '512x512',
30 | type: 'image/png',
31 | },
32 | {
33 | src: 'logo.svg',
34 | sizes: '165x165',
35 | type: 'image/svg',
36 | purpose: 'any maskable',
37 | },
38 | ],
39 | },
40 | workbox: {
41 | navigateFallbackDenylist: [/^\/new$/],
42 | globPatterns: ['**/*.{css,js,html,png,svg,ico,txt,woff2}'],
43 | runtimeCaching: [
44 | {
45 | urlPattern: pwaFontsRegex,
46 | handler: 'CacheFirst',
47 | options: {
48 | cacheName: 'google-fonts-cache',
49 | expiration: {
50 | maxEntries: 10,
51 | maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
52 | },
53 | cacheableResponse: {
54 | statuses: [0, 200],
55 | },
56 | },
57 | },
58 | {
59 | urlPattern: pwaFontStylesRegex,
60 | handler: 'CacheFirst',
61 | options: {
62 | cacheName: 'gstatic-fonts-cache',
63 | expiration: {
64 | maxEntries: 10,
65 | maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
66 | },
67 | cacheableResponse: {
68 | statuses: [0, 200],
69 | },
70 | },
71 | },
72 | {
73 | urlPattern: githubusercontentRegex,
74 | handler: 'CacheFirst',
75 | options: {
76 | cacheName: 'githubusercontent-images-cache',
77 | expiration: {
78 | maxEntries: 10,
79 | maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
80 | },
81 | cacheableResponse: {
82 | statuses: [0, 200],
83 | },
84 | },
85 | },
86 | ],
87 | },
88 | }
89 |
--------------------------------------------------------------------------------
/docs/.vitepress/scripts/transformHead.ts:
--------------------------------------------------------------------------------
1 | import type { HeadConfig, TransformContext } from 'vitepress'
2 |
3 | import { preconnectHomeLinks, preconnectLinks } from '../meta'
4 |
5 | export const transformHead = async ({ pageData }: TransformContext): Promise => {
6 | const head: HeadConfig[] = []
7 |
8 | const home = pageData.relativePath === 'index.md'
9 |
10 | ; (home ? preconnectHomeLinks : preconnectLinks).forEach((link) => {
11 | head.push(['link', { rel: 'dns-prefetch', href: link }])
12 | head.push(['link', { rel: 'preconnect', href: link }])
13 | })
14 |
15 | head.push(['link', { rel: 'prefetch', href: '/logo.svg' }])
16 | if (home)
17 | head.push(['link', { rel: 'prefetch', href: '/netlify.svg' }])
18 |
19 | return head
20 | }
21 |
--------------------------------------------------------------------------------
/docs/.vitepress/style/main.css:
--------------------------------------------------------------------------------
1 | .dark [img-light] {
2 | display: none;
3 | }
4 |
5 | html:not(.dark) [img-dark] {
6 | display: none;
7 | }
8 |
9 | /* Overrides */
10 |
11 | .VPSocialLink {
12 | transform: scale(0.9);
13 | }
14 |
15 | .vp-doc th, .vp-doc td {
16 | padding: 6px 10px;
17 | border: 1px solid #8882;
18 | }
19 |
20 | /* h3 breaks SEO => replaced with h2 with the same size */
21 | .home-content h2 {
22 | margin-top: 2rem;
23 | font-size: 1.35rem;
24 | border-bottom: none;
25 | margin-bottom: 0;
26 | }
27 |
28 | img.resizable-img {
29 | width: unset;
30 | height: unset;
31 | }
32 |
33 | /* fix height ~ 2 lines of text: 3 or more cards per row */
34 | .VPTeamMembersItem.small .profile .data .affiliation {
35 | min-height: 3rem;
36 | }
37 | .VPTeamMembersItem.small .profile .data .desc {
38 | min-height: 3rem;
39 | }
40 |
41 | /* fix height ~ 3 lines of text: 4 cards per row */
42 | @media (min-width: 1064px) and (max-width: 1143px) {
43 | .VPTeamMembersItem.small .profile .data .affiliation {
44 | min-height: 4rem;
45 | }
46 | .VPTeamMembersItem.small .profile .data .desc {
47 | min-height: 4rem;
48 | }
49 | }
50 | /* fix height ~ 3 lines of text: 3 cards per row */
51 | @media (min-width: 815px) and (max-width: 875px) {
52 | .VPTeamMembersItem.small .profile .data .affiliation {
53 | min-height: 4rem;
54 | }
55 | .VPTeamMembersItem.small .profile .data .desc {
56 | min-height: 4rem;
57 | }
58 | }
59 | /* fix height ~ 3 lines of text: 2 cards per row */
60 | @media (max-width: 612px) {
61 | .VPTeamMembersItem.small .profile .data .affiliation {
62 | min-height: 4rem;
63 | }
64 | .VPTeamMembersItem.small .profile .data .desc {
65 | min-height: 4rem;
66 | }
67 | }
68 | /* fix height: one card per row */
69 | @media (max-width: 568px) {
70 | .VPTeamMembersItem.small .profile .data .affiliation {
71 | min-height: unset;
72 | }
73 | .VPTeamMembersItem.small .profile .data .desc {
74 | min-height: unset;
75 | }
76 | }
77 |
78 |
79 | .demo button {
80 | padding: 3px 15px;
81 | background-color: var(--vp-c-brand);
82 | border: none;
83 | outline: none;
84 | color: #fff;
85 | margin: .5rem 0;
86 | border-bottom: 2px solid var(--vp-c-brand-dark);
87 | text-shadow: 1px 1px 1px var(--vp-c-brand-dark);
88 | border-radius: 4px;
89 | font-size: 1rem;
90 | box-sizing: border-box;
91 | vertical-align: middle
92 | }
93 |
--------------------------------------------------------------------------------
/docs/.vitepress/style/vars.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Colors
3 | * -------------------------------------------------------------------------- */
4 |
5 | :root {
6 | --vp-c-accent: #dab40b;
7 | --vp-c-brand: #6da13f;
8 | --vp-c-brand-light: #7ec242;
9 | --vp-c-brand-lighter: #93d31c;
10 | --vp-c-brand-dark: #668d11;
11 | --vp-c-brand-darker: #52730d;
12 | --vp-c-text-code: #5d6f5d;
13 | /* --vp-code-block-bg: #f1f1f1; */
14 | /* fix TIP on light: the default one is too light (2 in contrast) */
15 | --vp-custom-block-tip-text: rgb(12, 124, 108);
16 | --vp-custom-block-tip-border: rgba(12, 124, 108, 0.5);
17 | --vp-custom-block-tip-bg: rgba(18, 181, 157, 0.1);
18 | /* fix contrast on gray cards: used by --vp-c-text-2 */
19 | --vp-c-text-light-2: rgba(56 56 56 / 70%);
20 | /* fix contrast: lang name on gray code block */
21 | --vp-c-text-dark-3: rgba(180, 180, 180, 0.7);
22 | }
23 |
24 | .dark {
25 | --vp-code-block-bg: rgba(0,0,0,0.2);
26 | --vp-c-text-code: #c0cec0;
27 | /* fix TIP: check the same above (these are the defaults) */
28 | --vp-custom-block-tip-text: rgb(18, 181, 157);
29 | --vp-custom-block-tip-border: rgba(18, 181, 157, 0.5);
30 | --vp-custom-block-tip-bg: rgba(18, 181, 157, 0.1);
31 | /* fix contrast on gray cards: check the same above (this is the default) */
32 | --vp-c-text-dark-2: rgba(235, 235, 235, 0.60);
33 | /* fix lang name: check the same above (this is the default) */
34 | --vp-c-text-dark-3: rgba(235, 235, 235, 0.38);
35 | }
36 |
37 | /**
38 | * Component: Button
39 | * -------------------------------------------------------------------------- */
40 |
41 | :root {
42 | --vp-button-brand-border: var(--vp-c-brand-light);
43 | --vp-button-brand-text: var(--vp-c-text-dark-1);
44 | --vp-button-brand-bg: var(--vp-c-brand);
45 | --vp-button-brand-hover-border: var(--vp-c-brand-light);
46 | --vp-button-brand-hover-text: var(--vp-c-text-dark-1);
47 | --vp-button-brand-hover-bg: var(--vp-c-brand-light);
48 | --vp-button-brand-active-border: var(--vp-c-brand-light);
49 | --vp-button-brand-active-text: var(--vp-c-text-dark-1);
50 | --vp-button-brand-active-bg: var(--vp-button-brand-bg);
51 | }
52 |
53 | /**
54 | * Component: Home
55 | * -------------------------------------------------------------------------- */
56 |
57 | :root {
58 | --vp-home-hero-name-color: transparent;
59 | --vp-home-hero-name-background: -webkit-linear-gradient(
60 | 120deg,
61 | #86b91a 30%,
62 | #edd532
63 | );
64 | --vp-home-hero-image-background-image: linear-gradient(
65 | -45deg,
66 | #86b91a60 30%,
67 | #edd53260
68 | );
69 | --vp-home-hero-image-filter: blur(30px);
70 | }
71 |
72 | @media (min-width: 640px) {
73 | :root {
74 | --vp-home-hero-image-filter: blur(56px);
75 | }
76 | }
77 |
78 | @media (min-width: 960px) {
79 | :root {
80 | --vp-home-hero-image-filter: blur(72px);
81 | }
82 | }
83 |
84 |
85 | /**
86 | * Component: Algolia
87 | * -------------------------------------------------------------------------- */
88 |
89 | .DocSearch {
90 | --docsearch-primary-color: var(--vp-c-brand) !important;
91 | }
92 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.ts:
--------------------------------------------------------------------------------
1 | import { h } from 'vue'
2 | import Theme from 'vitepress/theme'
3 | import { inBrowser } from 'vitepress'
4 | import '../style/main.css'
5 | import '../style/vars.css'
6 | import 'uno.css'
7 | import HomePage from '../components/HomePage.vue'
8 | import PageTags from '../components/Tags.vue'
9 |
10 | if (inBrowser)
11 | import('./pwa')
12 |
13 | export default {
14 | ...Theme,
15 | Layout() {
16 | return h(Theme.Layout, null, {
17 | 'home-features-after': () => h(HomePage),
18 | 'doc-before': () => h(PageTags)
19 | })
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/pwa.ts:
--------------------------------------------------------------------------------
1 | import { registerSW } from 'virtual:pwa-register'
2 |
3 | registerSW({ immediate: true })
4 |
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | usepy.code05.com
--------------------------------------------------------------------------------
/docs/api/converter/to_bool.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # to_bool
6 | 将值转换为布尔类型
7 |
8 | ```python
9 | from usepy import to_bool
10 | ```
11 |
12 | ### 参数
13 |
14 | - `value`: 要转换的值
15 |
16 | ### 返回值
17 |
18 | - `bool`: 转换后的布尔值
19 |
20 | ### 例子
21 |
22 | ```python
23 | >>> to_bool(True)
24 | True
25 | >>> to_bool("True")
26 | True
27 | >>> to_bool("true")
28 | True
29 | >>> to_bool("t")
30 | True
31 | >>> to_bool(0)
32 | False
33 | >>> to_bool("false")
34 | False
35 | ```
36 |
--------------------------------------------------------------------------------
/docs/api/converter/to_list.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # to_list
6 | 将值转换为列表
7 |
8 | ```python
9 | from usepy import to_list
10 | ```
11 |
12 | ### 参数
13 |
14 | - `value`: 要转换的值
15 |
16 | ### 返回值
17 |
18 | - `list`: 转换后的列表
19 |
20 | ### 例子
21 |
22 | ```python
23 | >>> to_list([1, 2, 3])
24 | [1, 2, 3]
25 | >>> to_list("abc")
26 | ['a', 'b', 'c']
27 | >>> to_list(123)
28 | [123]
29 | >>> to_list((1, 2, 3))
30 | [1, 2, 3]
31 |
--------------------------------------------------------------------------------
/docs/api/converter/to_md5.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # to_md5
6 | 将值转换为MD5哈希
7 |
8 | ```python
9 | from usepy import to_md5
10 | ```
11 |
12 | ### 参数
13 |
14 | - `data`: 要转换的数据
15 |
16 | ### 返回值
17 |
18 | - `str`: MD5哈希值
19 |
20 | ### 例子
21 |
22 | ```python
23 | >>> to_md5("hello")
24 | '5d41402abc4b2a76b9719d911017c592'
25 | >>> to_md5(b"hello")
26 | '5d41402abc4b2a76b9719d911017c592'
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/api/converter/to_set.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # to_set
6 | 将值转换为集合
7 |
8 | ```python
9 | from usepy import to_set
10 | ```
11 |
12 | ### 参数
13 |
14 | - `value`: 要转换的值
15 |
16 | ### 返回值
17 |
18 | - `set`: 转换后的集合
19 |
20 | ### 例子
21 |
22 | ```python
23 | >>> to_set([1, 2, 2, 3])
24 | {1, 2, 3}
25 | >>> to_set("abcabc")
26 | {'a', 'b', 'c'}
27 | >>> to_set((1, 2, 3))
28 | {1, 2, 3}
29 |
--------------------------------------------------------------------------------
/docs/api/converter/to_string.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # to_string
6 | 将值转换为字符串
7 |
8 | ```python
9 | from usepy import to_string
10 | ```
11 |
12 | ### 参数
13 |
14 | - `value`: 要转换的值
15 |
16 | ### 返回值
17 |
18 | - `str`: 转换后的字符串
19 |
20 | ### 例子
21 |
22 | ```python
23 | >>> to_string(123)
24 | '123'
25 | >>> to_string([1, 2, 3])
26 | '1, 2, 3'
27 | >>> to_string({"a": 1, "b": 2})
28 | 'a: 1, b: 2'
29 | >>> to_string(None)
30 | ''
31 | ```
32 |
--------------------------------------------------------------------------------
/docs/api/date/format.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # format
6 | 格式化日期时间对象
7 |
8 | ```python
9 | from usepy import format
10 | ```
11 |
12 | ### 参数
13 |
14 | - `dt`: 要格式化的datetime对象
15 | - `fmt`: 格式化字符串(可选)
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> from datetime import datetime
21 | >>> dt = datetime(2023, 4, 1, 12, 30, 45)
22 | >>> format(dt)
23 | '2023-04-01 12:30:45'
24 | >>> format(dt, "%Y年%m月%d日 %H时%M分%S秒")
25 | '2023年04月01日 12时30分45秒'
26 | ```
27 |
--------------------------------------------------------------------------------
/docs/api/date/now.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # now
6 | 获取当前日期时间
7 |
8 | ```python
9 | from usepy import now
10 | ```
11 |
12 | ### 返回值
13 |
14 | - `datetime`: 当前日期时间对象
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> from usepy import now, format
20 | >>> current_time = now()
21 | >>> format(current_time)
22 | '2023-04-01 15:30:00' # 实际输出会根据当前时间而变化
23 | ```
24 | ```
25 |
26 |
--------------------------------------------------------------------------------
/docs/api/date/parse.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # parse
6 | 解析日期时间字符串
7 |
8 | ```python
9 | from usepy import parse
10 | ```
11 |
12 | ### 参数
13 |
14 | - `time_str`: 要解析的时间字符串
15 | - `fmt`: 时间字符串的格式(可选)
16 |
17 | ### 返回值
18 |
19 | - `datetime`: 解析后的日期时间对象
20 |
21 | ### 例子
22 |
23 | ```python
24 | >>> from usepy import parse, format
25 | >>> dt = parse("2023-04-01 12:30:45")
26 | >>> format(dt)
27 | '2023-04-01 12:30:45'
28 | >>> dt = parse("2023年04月01日 12时30分45秒")
29 | >>> format(dt)
30 | '2023-04-01 12:30:45'
31 | ```
32 | ```
33 |
34 |
35 |
--------------------------------------------------------------------------------
/docs/api/date/timestamp.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # timestamp
6 | 获取日期时间对象的时间戳
7 |
8 | ```python
9 | from usepy import timestamp
10 | ```
11 |
12 | ### 参数
13 |
14 | - `dt`: 日期时间对象(可选)
15 | - `digits`: 时间戳位数,10或13(可选)
16 |
17 | ### 返回值
18 |
19 | - `int`: 整数时间戳
20 |
21 | ### 例子
22 |
23 | ```python
24 | >>> from usepy import timestamp, now
25 | >>> timestamp() # 10位时间戳
26 | 1680339000
27 | >>> timestamp(digits=13) # 13位时间戳
28 | 1680339000000
29 | >>> dt = now()
30 | >>> timestamp(dt, digits=13)
31 | 1680339000000
32 | ```
33 | ```
34 |
35 |
--------------------------------------------------------------------------------
/docs/api/decorator/catch_error.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # catch_error
6 | 捕获函数执行过程中的错误并返回指定值
7 |
8 | ```python
9 | from usepy import catch_error
10 | ````
11 |
12 |
13 | ### 参数
14 |
15 | - `return_val`: 发生异常时返回的值(可选)
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> import logging
21 | >>> logging.basicConfig(level=logging.DEBUG)
22 | >>>
23 | >>> @catch_error(return_val=0)
24 | ... def divide(a, b):
25 | ... return a / b
26 | >>>
27 | >>> divide(10, 2)
28 | 5.0
29 | >>> divide(10, 0)
30 | 0
31 | ```
32 |
--------------------------------------------------------------------------------
/docs/api/decorator/retry.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # retry
6 | 为函数添加重试机制,支持异步函数
7 |
8 | ```python
9 | from usepy import retry
10 | ````
11 |
12 |
13 | ### 参数
14 |
15 | - `max_attempts`: 最大重试次数
16 | - `retry_interval`: 重试间隔时间(秒)
17 | - `retry_exceptions`: 需要重试的异常类型(可选)
18 |
19 | ### 例子
20 |
21 | ```python
22 | >>> @retry(max_attempts=3, retry_interval=1)
23 | ... def unstable_function():
24 | ... import random
25 | ... if random.random() < 0.7:
26 | ... raise ValueError("随机错误")
27 | ... return "成功"
28 | >>>
29 | >>> unstable_function()
30 | '成功'
31 | ```
32 |
--------------------------------------------------------------------------------
/docs/api/decorator/singleton.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # singleton
6 | 实现单例模式的装饰器
7 |
8 | ```python
9 | from usepy import singleton
10 | ````
11 |
12 |
13 | ### 例子
14 |
15 | ```python
16 | >>> @singleton
17 | ... class MyClass:
18 | ... def __init__(self, value):
19 | ... self.value = value
20 | >>>
21 | >>> instance1 = MyClass(42)
22 | >>> instance2 = MyClass(24)
23 | >>> instance1.value
24 | 42
25 | >>> instance1 is instance2
26 | True
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/api/decorator/throttle.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # throttle
6 | 限制函数调用频率的装饰器
7 |
8 | ```python
9 | from usepy import throttle
10 | ````
11 |
12 |
13 | ### 参数
14 |
15 | - `delay`: 允许再次调用的时间间隔(秒)
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> import time
21 | >>>
22 | >>> @throttle(delay=2)
23 | ... def print_message():
24 | ... print("函数被调用")
25 | >>>
26 | >>> print_message()
27 | 函数被调用
28 | >>> print_message() # 立即调用,不会执行
29 | >>> time.sleep(2)
30 | >>> print_message()
31 | 函数被调用
32 | ```
33 |
34 | ```
35 |
36 |
37 |
--------------------------------------------------------------------------------
/docs/api/dict/ad_dict.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # ad_dict
6 | 创建一个允许使用点号访问的字典
7 |
8 | ```python
9 | from usepy import ad_dict
10 | ```
11 |
12 | ### 参数
13 |
14 | - `data`: 初始化字典数据(可选)
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> d = ad_dict({'a': 1, 'b': {'c': 2}})
20 | >>> d.a
21 | 1
22 | >>> d.b.c
23 | 2
24 | >>> d.d = 3
25 | >>> d
26 | {'a': 1, 'b': {'c': 2}, 'd': 3}
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/api/dict/merge_dicts.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # merge_dicts
6 | 合并多个字典
7 |
8 | ```python
9 | from usepy import merge_dicts
10 | ```
11 |
12 | ### 参数
13 |
14 | - `*dicts`: 要合并的字典(可变参数)
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> merge_dicts({'a': 1, 'b': 2}, {'c': 3}, {'d': 4})
20 | {'a': 1, 'b': 2, 'c': 3, 'd': 4}
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/api/dict/sort_by_key.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # sort_by_key
6 | 根据字典的key排序
7 |
8 | ```python
9 | from usepy import sort_by_key
10 | ```
11 |
12 | ### 参数
13 |
14 | - `original_dict`: 要排序的字典
15 | - `az`: 排序方式,默认为True,表示升序
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> sort_by_key({'c': 1, 'b': 2, 'a': 3})
21 | {'a': 3, 'b': 2, 'c': 1}
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/api/dict/sort_by_value.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # sort_by_value
6 | 根据字典的value排序
7 |
8 | ```python
9 | from usepy import sort_by_value
10 | ```
11 |
12 | ### 参数
13 |
14 | - `original_dict`: 要排序的字典
15 | - `az`: 排序方式,默认为True,表示升序
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> sort_by_value({'c': 1, 'b': 2, 'a': 3})
21 | {'c': 1, 'b': 2, 'a': 3}
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/api/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # 方法总览
6 |
7 |
--------------------------------------------------------------------------------
/docs/api/list/chunk.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # chunk
6 | 将列表分割成指定大小的子列表。
7 |
8 | ```python
9 | from usepy import chunk
10 | ```
11 |
12 | ### 参数
13 |
14 | - `lst`: 要分割的列表
15 | - `size`: 每个子列表的大小
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> chunk([1, 2, 3, 4, 5], 2)
21 | [[1, 2], [3, 4], [5]]
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/api/list/compact.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # compact
6 | 将列表压缩。
7 |
8 | ```python
9 | from usepy import compact
10 | ```
11 |
12 | ### 参数
13 |
14 | - `lst`: 列表
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> compact([0, 1, False, 2, '', 3, None, 4, {}, 5])
20 | [1, 2, 3, 4, 5]
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/api/list/count_by.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # count_by
6 | 根据指定的函数对列表元素进行计数。
7 |
8 | ```python
9 | from usepy import count_by
10 | ```
11 |
12 | ### 参数
13 |
14 | - `lst`: 要计数的列表
15 | - `fn`: 用于分类的函数
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> count_by([1.2, 2.3, 3.4, 4.5], math.floor)
21 | {1: 1, 2: 1, 3: 1, 4: 1}
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/api/list/difference.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # difference
6 | 返回两个列表的差集。
7 |
8 | ```python
9 | from usepy import difference
10 | ```
11 |
12 | ### 参数
13 |
14 | - `lst1`: 第一个列表
15 | - `lst2`: 第二个列表
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> difference([1, 2, 3], [2, 3, 4])
21 | [1]
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/api/list/every.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # every
6 | 检查列表中的所有元素是否都满足指定条件。
7 |
8 | ```python
9 | from usepy import every
10 | ```
11 |
12 | ### 参数
13 |
14 | - `lst`: 要检查的列表
15 | - `fn`: 用于检查的函数
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> every([2, 4, 6, 8], lambda x: x % 2 == 0)
21 | True
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/api/list/flatten.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # flatten
6 | 将嵌套的列表展平一层。
7 |
8 | ```python
9 | from usepy import flatten
10 | ```
11 |
12 | ### 参数
13 |
14 | - `lst`: 要展平的嵌套列表
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> flatten([1, [2, 3, [4, 5]], 6])
20 | [1, 2, 3, [4, 5], 6]
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/api/list/flatten_deep.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # flatten_deep
6 | 将嵌套的列表完全展平。
7 |
8 | ```python
9 | from usepy import flatten_deep
10 | ```
11 |
12 | ### 参数
13 |
14 | - `lst`: 要完全展平的嵌套列表
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> flatten_deep([1, [2, 3, [4, 5]], 6])
20 | [1, 2, 3, 4, 5, 6]
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/api/list/key_by.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # key_by
6 | 将列表转换为字典,使用指定的键函数生成键。
7 |
8 | ```python
9 | from usepy import key_by
10 | ```
11 |
12 | ### 参数
13 |
14 | - `lst`: 源列表
15 | - `key_fn`: 用于生成键的函数
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> key_by(['a', 'bb', 'ccc'], len)
21 | {1: 'a', 2: 'bb', 3: 'ccc'}
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/api/list/sample.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # sample
6 | 从列表中随机选择指定数量的唯一元素。
7 |
8 | ```python
9 | from usepy import sample
10 | ```
11 |
12 | ### 参数
13 |
14 | - `lst`: 源列表
15 | - `n`: 要选择的元素数量
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> sample([1, 2, 3, 4, 5], 3)
21 | [4, 1, 5] # 结果可能会不同,因为是随机选择
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/api/list/shuffle.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # shuffle
6 | 随机打乱列表中的元素顺序。
7 |
8 | ```python
9 | from usepy import shuffle
10 | ```
11 |
12 | ### 参数
13 |
14 | - `lst`: 要打乱的列表
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> lst = [1, 2, 3, 4, 5]
20 | >>> shuffle(lst)
21 | >>> lst
22 | [3, 1, 4, 5, 2] # 结果可能会不同,因为是随机打乱
23 | ```
24 |
--------------------------------------------------------------------------------
/docs/api/list/some.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # some
6 | 检查列表中是否存在至少一个元素满足指定条件。
7 |
8 | ```python
9 | from usepy import some
10 | ```
11 |
12 | ### 参数
13 |
14 | - `lst`: 要检查的列表
15 | - `fn`: 用于检查的函数
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> some([1, 2, 3, 4], lambda x: x > 3)
21 | True
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/api/list/union.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # union
6 | 返回多个列表的并集,去除重复元素。
7 |
8 | ```python
9 | from usepy import union
10 | ```
11 |
12 | ### 参数
13 |
14 | - `*lists`: 要合并的列表
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> union([1, 2, 3], [2, 3, 4], [3, 4, 5])
20 | [1, 2, 3, 4, 5]
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/api/list/uniq.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # uniq
6 | 返回一个新列表,去除重复元素。
7 |
8 | ```python
9 | from usepy import uniq
10 | ```
11 |
12 | ### 参数
13 |
14 | - `lst`: 源列表
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> uniq([1, 2, 2, 3, 4, 4, 5])
20 | [1, 2, 3, 4, 5]
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/api/list/without.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # without
6 | 返回一个新列表,排除指定的值。
7 |
8 | ```python
9 | from usepy import without
10 | ```
11 |
12 | ### 参数
13 |
14 | - `lst`: 源列表
15 | - `*values`: 要排除的值
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> without([1, 2, 3, 4, 5], 2, 4)
21 | [1, 3, 5]
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/api/list/zip_dict.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # zip_dict
6 | 将多个列表打包成字典。
7 |
8 | ```python
9 | from usepy import zip_dict
10 | ```
11 |
12 | ### 参数
13 |
14 | - `*lists`: 要打包的列表zip_dict
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> zip_dict([1, 2, 3], ['a', 'b', 'c'])
20 | {1: 'a', 2: 'b', 3: 'c'}
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/api/list/zip_tuple.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # zip_tuple
6 | 将多个列表打包成元组列表。
7 |
8 | ```python
9 | from usepy import zip_tuple
10 | ```
11 |
12 | ### 参数
13 |
14 | - `*lists`: 要打包的列表zip_tuple
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> zip_tuple([1, 2, 3], ['a', 'b', 'c'])
20 | [(1, 'a'), (2, 'b'), (3, 'c')]
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/api/misc/dynamic_import.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # dynamic_import
6 | 从字符串动态导入模块和函数
7 |
8 | ```python
9 | from usepy import dynamic_import
10 | ```
11 |
12 | ### 参数
13 |
14 | - `module_string`: 要导入的模块和函数的字符串表示
15 |
16 | ### 返回值
17 |
18 | - `tuple`: 包含导入的模块和函数的元组
19 |
20 | ### 例子
21 |
22 | ```python
23 | >>> module, function = dynamic_import("json.dumps")
24 | >>> function.__name__
25 | 'dumps'
26 | >>> module.__name__
27 | 'json'
28 |
29 | >>> module, function = dynamic_import("os.path.join")
30 | >>> function.__name__
31 | 'join'
32 | >>> module.__name__
33 | 'posixpath' # 或 'ntpath',取决于操作系统
34 |
35 | >>> module, _ = dynamic_import("sys")
36 | >>> module.__name__
37 | 'sys'
38 | ```
39 |
--------------------------------------------------------------------------------
/docs/api/misc/get_function_name.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # get_function_name
6 | 获取当前函数的名称
7 |
8 | ```python
9 | from usepy import get_function_name
10 | ```
11 |
12 | ### 返回值
13 |
14 | - `str`: 当前函数的名称
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> def outer():
20 | ... def inner():
21 | ... return get_function_name()
22 | ... return inner()
23 | >>> outer()
24 | 'inner'
25 |
26 | >>> def example_function():
27 | ... print(get_function_name())
28 | >>> example_function()
29 | example_function
30 |
31 | >>> lambda_func = lambda: get_function_name()
32 | >>> lambda_func()
33 | ''
34 |
--------------------------------------------------------------------------------
/docs/api/string/camel_case.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # camel_case
6 | 将字符串转换为驼峰命名法
7 |
8 | ```python
9 | from usepy import camel_case
10 | ```
11 |
12 | ### 参数
13 |
14 | - `string`: 要转换的字符串
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> camel_case('camelCase')
20 | 'camelCase'
21 | >>> camel_case('some whitespace')
22 | 'someWhitespace'
23 | >>> camel_case('hyphen-text')
24 | 'hyphenText'
25 | >>> camel_case('HTTPRequest')
26 | 'httpRequest'
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/api/string/capitalize.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # capitalize
6 | 将字符串的第一个字符转换为大写,其余字符转换为小写
7 |
8 | ```python
9 | from usepy import capitalize
10 | ```
11 |
12 | ### 参数
13 |
14 | - `string`: 要转换的字符串
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> capitalize('fred')
20 | 'Fred'
21 | >>> capitalize('FRED')
22 | 'Fred'
23 | ```
24 |
--------------------------------------------------------------------------------
/docs/api/string/kebab_case.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # kebab_case
6 | 将字符串转换为短横线命名法
7 |
8 | ```python
9 | from usepy import kebab_case
10 | ```
11 |
12 | ### 参数
13 |
14 | - `string`: 要转换的字符串
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> kebab_case('camelCase')
20 | 'camel-case'
21 | >>> kebab_case('some whitespace')
22 | 'some-whitespace'
23 | >>> kebab_case('hyphen-text')
24 | 'hyphen-text'
25 | >>> kebab_case('HTTPRequest')
26 | 'http-request'
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/api/string/left.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # left
6 | 获取字符串中指定子串左侧的部分
7 |
8 | ```python
9 | from usepy import left
10 | ```
11 |
12 | ### 参数
13 |
14 | - `original_str`: 原始字符串
15 | - `end_str`: 结束子串
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> left('abc123def', 'def')
21 | 'abc123'
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/api/string/lower_case.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # lower_case
6 | 将字符串转换为小写并用空格分隔单词
7 |
8 | ```python
9 | from usepy import lower_case
10 | ```
11 |
12 | ### 参数
13 |
14 | - `string`: 要转换的字符串
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> lower_case('camelCase')
20 | 'camel case'
21 | >>> lower_case('some whitespace')
22 | 'some whitespace'
23 | >>> lower_case('hyphen-text')
24 | 'hyphen text'
25 | >>> lower_case('HTTPRequest')
26 | 'http request'
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/api/string/middle.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # middle
6 | 获取字符串中两个指定子串之间的部分
7 |
8 | ```python
9 | from usepy import middle
10 | ```
11 |
12 | ### 参数
13 |
14 | - `original_str`: 原始字符串
15 | - `start_str`: 开始子串(可选)
16 | - `end_str`: 结束子串(可选)
17 |
18 | ### 例子
19 |
20 | ```python
21 | >>> middle('abc123def', 'abc', 'def')
22 | '123'
23 | ```
24 |
--------------------------------------------------------------------------------
/docs/api/string/middle_batch.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # middle_batch
6 | 获取字符串中两个指定子串之间的多个部分
7 |
8 | ```python
9 | from usepy import middle_batch
10 | ```
11 |
12 | ### 参数
13 |
14 | - `original_str`: 原始字符串
15 | - `start_str`: 开始子串(可选)
16 | - `end_str`: 结束子串(可选)
17 | - `max_count`: 最大返回数量(可选)
18 |
19 | ### 例子
20 |
21 | ```python
22 | >>> middle_batch('abc123def456abc789def', 'abc', 'def')
23 | ['123', '789']
24 | >>> middle_batch('abc123def456abc789def', 'abc', 'def', 1)
25 | ['123']
26 | ```
27 |
--------------------------------------------------------------------------------
/docs/api/string/pascal_case.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # pascal_case
6 | 将字符串转换为帕斯卡命名法
7 |
8 | ```python
9 | from usepy import pascal_case
10 | ```
11 |
12 | ### 参数
13 |
14 | - `string`: 要转换的字符串
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> pascal_case('pascalCase')
20 | 'PascalCase'
21 | >>> pascal_case('some whitespace')
22 | 'SomeWhitespace'
23 | >>> pascal_case('hyphen-text')
24 | 'HyphenText'
25 | >>> pascal_case('HTTPRequest')
26 | 'HttpRequest'
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/api/string/right.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # right
6 | 获取字符串中指定子串右侧的部分
7 |
8 | ```python
9 | from usepy import right
10 | ```
11 |
12 | ### 参数
13 |
14 | - `original_str`: 原始字符串
15 | - `start_str`: 开始子串
16 |
17 | ### 例子
18 |
19 | ```python
20 | >>> right('abc123def', 'abc')
21 | '123def'
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/api/string/snake_case.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # snake_case
6 | 将字符串转换为下划线命名法
7 |
8 | ```python
9 | from usepy import snake_case
10 | ```
11 |
12 | ### 参数
13 |
14 | - `string`: 要转换的字符串
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> snake_case('camelCase')
20 | 'camel_case'
21 | >>> snake_case('some whitespace')
22 | 'some_whitespace'
23 | >>> snake_case('hyphen-text')
24 | 'hyphen_text'
25 | >>> snake_case('HTTPRequest')
26 | 'http_request'
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/api/useSnowflakeId.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 | ## useSnowflakeId
5 |
6 | 用于生成分布式唯一ID。
7 |
8 | ```python
9 | from usepy import useSnowflakeId
10 |
11 | snowflake = useSnowflakeId()
12 | snowflake.generate_id() # 生成唯一ID
13 | ```
14 |
15 | ### 参数
16 |
17 | - `datacenter_id`:数据中心ID,范围为0-31,可选,默认为1
18 | - `worker_id`:工作机器ID,范围为0-31,可选,默认为1
19 | - `sequence`:序列号,范围为0-4095,占12位,可选,默认为0
20 |
21 | ### 返回值
22 |
23 | - `id`:唯一ID
24 |
--------------------------------------------------------------------------------
/docs/api/validator/is_async_function.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # is_async_function
6 | 检查给定的函数是否为异步函数
7 |
8 | ```python
9 | from usepy import is_async_function
10 | ```
11 |
12 | ### 参数
13 |
14 | - `func`: 要检查的函数
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> import asyncio
20 | >>> async def async_func():
21 | ... await asyncio.sleep(1)
22 | >>> def sync_func():
23 | ... pass
24 | >>> is_async_function(async_func)
25 | True
26 | >>> is_async_function(sync_func)
27 | False
28 | >>> is_async_function(lambda x: x)
29 | False
30 | ```
31 |
--------------------------------------------------------------------------------
/docs/api/validator/is_url.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # is_url
6 | 检查给定的字符串是否为有效的URL
7 |
8 | ```python
9 | from usepy import is_url
10 | ```
11 |
12 | ### 参数
13 |
14 | - `url`: 要检查的字符串
15 |
16 | ### 例子
17 |
18 | ```python
19 | >>> is_url("https://www.google.com")
20 | True
21 | >>> is_url("http://localhost:8080")
22 | True
23 | >>> is_url("ftp://example.com")
24 | False
25 | >>> is_url("not a url")
26 | False
27 | >>> is_url("")
28 | False
29 | ```
30 |
--------------------------------------------------------------------------------
/docs/ecology/index.md:
--------------------------------------------------------------------------------
1 | # 生态
2 |
3 | 大部分基础较为常用的功能内置于`usepy`中,但是有一些功能并不是所有人都需要,所以我们将这些功能放在了扩展中,这样可以保证`usepy`更加轻量化。
4 |
5 | ## 仓库列表
6 |
7 | - [use-rabbitmq](https://github.com/use-py/use-rabbitmq)
8 | - [use-nacos](https://github.com/use-py/use-nacos)
9 | - [use-redis](https://github.com/use-py/use-redis)
10 | - [use-logger](https://github.com/use-py/use-logger)
11 | - [use-notify](https://github.com/use-py/use-notify)
12 |
--------------------------------------------------------------------------------
/docs/ecology/logger.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: usepy-plugin-logger
3 | outline: deep
4 | ---
5 | # usepy-plugin-logger
6 |
7 | ::: code-group
8 |
9 | ```bash [pip]
10 | pip install "usepy[logger]"
11 | ```
12 | ```bash [poetry]
13 | poetry add "usepy[logger]"
14 | ```
15 | :::
16 |
17 |
18 | `loguru`是一个十分优秀的日志库,但是大部分第三方库都是使用`logging`模块来进行日志记录的,当项目中同时使用了`loguru`和`logging`时,会导致日志记录混乱。
19 |
20 | `useLogger`就是为了解决这个问题而生的,它可以将`logging`模块的日志记录转换为`loguru`的日志记录。并且能够统一格式输出。
21 |
22 | 当你在**项目入口处**使用`useLogger`后,你可以在任何地方使用`logging`/`loguru`模块来进行日志记录,它们统统会被无感转换为`loguru`的日志记录。
23 |
24 |
25 | ## 使用
26 |
27 | ```python
28 | from usepy import useLogger
29 |
30 | useLogger() # 使用默认配置
31 | ```
32 |
33 | 如果你自身项目正在使用`loguru`,这一切似乎感觉毫无变化。因为默认的配置只是修改了一点输出样式。
34 |
35 | 如果想要感受它带来的“魔法”,需要稍微配置一下。
36 |
37 | ```python
38 | from usepy import useLogger
39 |
40 | useLogger(packages=["scrapy", "django", "usepy"])
41 | ```
42 | 如果你在使用如`scrapy`/`django`等第三方库时,你会发现它们的日志记录也被统一了。
43 |
44 | 晚一些时候,这里会提供演示。
45 |
46 | ## Logstash/Filebeat
47 |
48 | 日志的更重要能力是将日志记录发送到`Logstash`/`Filebeat`,这样就可以将日志记录存储到`Elasticsearch`中,方便进行日志分析。所以统一日志的最终输出格式是非常重要的。
49 |
50 | `useLogger`内置一个`logstash_handler`统一化输出格式。
51 |
52 | ```python{6}
53 | from usepy import useTimeIt, useLogger, logstash_handler
54 |
55 | useLogger(
56 | handlers=[
57 | logstash_handler(level="DEBUG", extra={"app_name": "spider"})
58 | ],
59 | packages=["usepy"], # hook拦截 usepy 的日志
60 | extra={"project_name": "usepy"}
61 | )
62 |
63 | logger.warning("test warning")
64 | logger.info("test info")
65 | logger.debug("test debug")
66 | # 这里测试调用函数的耗时,这是一个在usepy包中的函数
67 | useTimeIt(lambda: logger.debug("start run test function"))()
68 | ```
69 |
70 | 运行结果:
71 |
72 | 
73 |
74 | 有了以上输出,如果你使用过类似`filebeat`的工具,你就可以通过它自动收集docker的日志产物,发往`elasticsearch`中,方便进行日志分析。
75 |
76 |
77 | ## 另类模块
78 |
79 | ### uvicorn
80 |
81 | `uvicorn`是一个非常优秀的`ASGI`服务器。它是`fastapi`的最佳拍档。它的日志拦截稍微特殊,我们将它单独拿出来。
82 |
83 | ```python
84 | # app.py
85 | from fastapi import FastAPI
86 | from usepy import useLoggerInterceptUvicorn
87 |
88 | useLoggerInterceptUvicorn() # 在 app 实例化前调用即可
89 |
90 | app = FastAPI()
91 |
92 | @app.get("/")
93 | def home():
94 | return {"message": "hello"}
95 | ```
96 |
97 | ```python
98 | # main.py
99 | import uvicorn
100 |
101 | uvicorn.run(app="app:app", host="127.0.0.1")
102 | ```
103 |
104 | 
105 |
106 | ## 兼容性
107 |
108 | `useLogger`兼容`loguru`和`logging`模块,你可以在任何地方使用它们来进行日志记录。
109 |
110 | 当你需要其他handler时,可以使用`loguru`的`add`方法来添加。
111 |
112 | ```python
113 | from loguru import logger
114 | from usepy import useLogger
115 |
116 | useLogger()
117 |
118 | logger.add(
119 | "file_{time}.log",
120 | rotation="00:00",
121 | retention="10 days",
122 | enqueue=True,
123 | encoding="utf-8",
124 | level="DEBUG",
125 | )
126 | ```
127 |
128 | ```text
129 | # file_2023-02-28_22-26-56_570490.log
130 | 2023-02-28 22:26:56.590 | WARNING | __main__::50 - test warning
131 | 2023-02-28 22:26:56.593 | INFO | __main__::51 - test info
132 | 2023-02-28 22:26:56.593 | DEBUG | __main__::52 - test debug
133 | 2023-02-28 22:26:56.593 | DEBUG | __main__::53 - start run test function
134 | 2023-02-28 22:26:56.594 | DEBUG | usepy.decorator.timeit:_timer:18 - took 0 seconds
135 | ```
136 |
--------------------------------------------------------------------------------
/docs/ecology/notify.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: usepy-plugin-notify
3 | outline: deep
4 | ---
5 |
6 | # usepy-plugin-logger
7 |
8 | ::: code-group
9 |
10 | ```bash [pip]
11 | pip install "usepy[notify]"
12 | ```
13 | ```bash [poetry]
14 | poetry add "usepy[notify]"
15 | ```
16 | :::
17 |
18 | 一个简单可扩展的消息通知库。
19 |
20 | 支持的消息通知渠道列表:
21 |
22 | - Wechat
23 | - Ding
24 | - Bark
25 | - Email
26 | - Chanify
27 | - Pushdeer
28 | - Pushover
29 |
30 | #### 使用
31 |
32 | ```python
33 | from usepy import useNotify, useNotifyChannels
34 |
35 | notify = useNotify()
36 | notify.add(
37 | # 添加多个通知渠道
38 | useNotifyChannels.Bark({"token": "xxxxxx"}),
39 | useNotifyChannels.Ding({
40 | "token": "xxxxx",
41 | "at_all": True
42 | })
43 | )
44 |
45 | notify.publish(title="消息标题", content="消息正文")
46 |
47 | ```
48 |
49 |
50 | #### 自己开发消息通知
51 |
52 | ```python
53 | from usepy.useNotifyChannels import BaseChannel
54 |
55 |
56 | class Custom(BaseChannel):
57 | """自定义消息通知"""
58 |
59 | def send(self, *args, **kwargs):
60 | ...
61 | ```
62 |
--------------------------------------------------------------------------------
/docs/extension/index.md:
--------------------------------------------------------------------------------
1 | # 扩展
2 |
3 | 大部分基础较为常用的功能内置于`usepy`中,但是有一些功能并不是所有人都需要,所以我们将这些功能放在了扩展中,这样可以保证`usepy`更加轻量化。
4 |
5 | ## 扩展列表
6 |
7 | - [use-rabbitmq](rabbitmq.md)
8 | - [use-redis](redis.md)
9 | - [use-logger](logger.md)
10 | - [use-notify](notify.md)
11 |
--------------------------------------------------------------------------------
/docs/extension/logger.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: use-logger
3 | outline: deep
4 | ---
5 | # use-logger
6 |
7 | ::: code-group
8 |
9 | ```bash [pip]
10 | pip install use-logger
11 | ```
12 | ```bash [poetry]
13 | poetry add use-logger
14 | ```
15 | :::
16 |
17 |
18 | `loguru`是一个十分优秀的日志库,但是大部分第三方库都是使用`logging`模块来进行日志记录的,当项目中同时使用了`loguru`和`logging`时,会导致日志记录混乱。
19 |
20 | `useLogger`就是为了解决这个问题而生的,它可以将`logging`模块的日志记录转换为`loguru`的日志记录。并且能够统一格式输出。
21 |
22 | 当你在**项目入口处**使用`useLogger`后,你可以在任何地方使用`logging`/`loguru`模块来进行日志记录,它们统统会被无感转换为`loguru`的日志记录。
23 |
24 |
25 | ## 使用
26 |
27 | ```python
28 | from use_logger import useLogger
29 |
30 | useLogger() # 使用默认配置
31 | ```
32 |
33 | 如果你自身项目正在使用`loguru`,这一切似乎感觉毫无变化。因为默认的配置只是修改了一点输出样式。
34 |
35 | 如果想要感受它带来的“魔法”,需要稍微配置一下。
36 |
37 | ```python
38 | from use_logger import useLogger
39 |
40 | useLogger(packages=["scrapy", "django", "usepy"])
41 | ```
42 | 如果你在使用如`scrapy`/`django`等第三方库时,你会发现它们的日志记录也被统一了。
43 |
44 | 晚一些时候,这里会提供演示。
45 |
46 | ## Logstash/Filebeat
47 |
48 | 日志的更重要能力是将日志记录发送到`Logstash`/`Filebeat`,这样就可以将日志记录存储到`Elasticsearch`中,方便进行日志分析。所以统一日志的最终输出格式是非常重要的。
49 |
50 | `useLogger`内置一个`logstash_handler`统一化输出格式。
51 |
52 | ```python{6}
53 | from usepy import useTimeIt
54 | from use_logger import useLogger
55 | from use_logger.handlers import logstash_handler
56 |
57 | useLogger(
58 | handlers=[
59 | logstash_handler(level="DEBUG", extra={"app_name": "spider"})
60 | ],
61 | packages=["usepy"], # hook拦截 usepy 的日志
62 | extra={"project_name": "usepy"}
63 | )
64 |
65 | logger.warning("test warning")
66 | logger.info("test info")
67 | logger.debug("test debug")
68 | # 这里测试调用函数的耗时,这是一个在usepy包中的函数
69 | useTimeIt(lambda: logger.debug("start run test function"))()
70 | ```
71 |
72 | 运行结果:
73 |
74 | 
75 |
76 | 有了以上输出,如果你使用过类似`filebeat`的工具,你就可以通过它自动收集docker的日志产物,发往`elasticsearch`中,方便进行日志分析。
77 |
78 |
79 | ## 另类模块
80 |
81 | ### uvicorn
82 |
83 | `uvicorn`是一个非常优秀的`ASGI`服务器。它是`fastapi`的最佳拍档。它的日志拦截稍微特殊,我们将它单独拿出来。
84 |
85 | ```python
86 | # app.py
87 | from fastapi import FastAPI
88 | from use_logger import useLoggerInterceptUvicorn
89 |
90 | useLoggerInterceptUvicorn() # 在 app 实例化前调用即可
91 |
92 | app = FastAPI()
93 |
94 | @app.get("/")
95 | def home():
96 | return {"message": "hello"}
97 | ```
98 |
99 | ```python
100 | # main.py
101 | import uvicorn
102 |
103 | uvicorn.run(app="app:app", host="127.0.0.1")
104 | ```
105 |
106 | 
107 |
108 | ## 兼容性
109 |
110 | `useLogger`兼容`loguru`和`logging`模块,你可以在任何地方使用它们来进行日志记录。
111 |
112 | 当你需要其他handler时,可以使用`loguru`的`add`方法来添加。
113 |
114 | ```python
115 | from loguru import logger
116 | from use_logger import useLogger
117 |
118 | useLogger()
119 |
120 | logger.add(
121 | "file_{time}.log",
122 | rotation="00:00",
123 | retention="10 days",
124 | enqueue=True,
125 | encoding="utf-8",
126 | level="DEBUG",
127 | )
128 | ```
129 |
130 | ```text
131 | # file_2023-02-28_22-26-56_570490.log
132 | 2023-02-28 22:26:56.590 | WARNING | __main__::50 - test warning
133 | 2023-02-28 22:26:56.593 | INFO | __main__::51 - test info
134 | 2023-02-28 22:26:56.593 | DEBUG | __main__::52 - test debug
135 | 2023-02-28 22:26:56.593 | DEBUG | __main__::53 - start run test function
136 | 2023-02-28 22:26:56.594 | DEBUG | usepy.decorator.timeit:_timer:18 - took 0 seconds
137 | ```
138 |
--------------------------------------------------------------------------------
/docs/extension/notify.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: use-notify
3 | outline: deep
4 | ---
5 |
6 | # use-notify
7 |
8 | ::: code-group
9 |
10 | ```bash [pip]
11 | pip install use-notify
12 | ```
13 | ```bash [poetry]
14 | poetry add use-notify
15 | ```
16 | :::
17 |
18 | 一个简单可扩展的消息通知库。
19 |
20 | 支持的消息通知渠道列表:
21 |
22 | - Wechat
23 | - Ding
24 | - Bark
25 | - Email
26 | - Chanify
27 | - Pushdeer
28 | - Pushover
29 |
30 | #### 使用
31 |
32 | ```python
33 | from use_notify import useNotify, useNotifyChannels
34 |
35 | notify = useNotify()
36 | notify.add(
37 | # 添加多个通知渠道
38 | useNotifyChannels.Bark({"token": "xxxxxx"}),
39 | useNotifyChannels.Ding({
40 | "token": "xxxxx",
41 | "at_all": True
42 | })
43 | )
44 |
45 | notify.publish(title="消息标题", content="消息正文")
46 |
47 | ```
48 |
49 |
50 | #### 自己开发消息通知
51 |
52 | ```python
53 | from use_notify.useNotifyChannels import BaseChannel
54 |
55 |
56 | class Custom(BaseChannel):
57 | """自定义消息通知"""
58 |
59 | def send(self, *args, **kwargs):
60 | ...
61 | ```
62 |
--------------------------------------------------------------------------------
/docs/extension/rabbitmq.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: use-rabbitmq
3 | outline: deep
4 | ---
5 |
6 | # use-rabbitmq
7 |
8 | ::: code-group
9 |
10 | ```bash [pip]
11 | pip install use-rabbitmq
12 | ```
13 | ```bash [poetry]
14 | poetry add use-rabbitmq
15 | ```
16 | :::
17 |
18 | 一个永不断线的RabbitMQ连接管理
19 |
20 |
21 | ### example
22 |
23 | ```python
24 | from use_rabbitmq import useRabbitMQ
25 |
26 | rmq = useRabbitMQ()
27 |
28 |
29 | @rmq.listener(queue_name="test")
30 | def test_listener(message):
31 | print(message.body)
32 | message.ack() # ack message
33 | ```
34 |
35 | if you use it with [usepy](https://github.com/use-py/usepy), you can use it like this:
36 |
37 | ```python
38 | from usepy.plugin import useRabbitMQ
39 |
40 | rmq = useRabbitMQ()
41 | ```
42 |
--------------------------------------------------------------------------------
/docs/extension/redis.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: use-redis
3 | outline: deep
4 | ---
5 |
6 | # use-redis
7 |
8 | ::: code-group
9 |
10 | ```bash [pip]
11 | pip install use-redis
12 | ```
13 | ```bash [poetry]
14 | poetry add use-redis
15 | ```
16 | :::
17 |
18 | 一个永不断线的Redis连接管理
19 |
20 |
21 | ### example
22 |
23 | ```python
24 | from use_redis import useRedis
25 |
26 | rds = useRedis()
27 | ```
28 |
29 | if you use it with [usepy](https://github.com/use-py/usepy), you can use it like this:
30 |
31 | ```python
32 | from usepy.plugin import useRedis
33 |
34 | rds = useRedis()
35 | ```
36 |
--------------------------------------------------------------------------------
/docs/guide/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/use-py/usepy/44e2436da3d95b3185a7669daa0af27674326a64/docs/guide/.DS_Store
--------------------------------------------------------------------------------
/docs/guide/features.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 功能 | 指南
3 | outline: deep
4 | ---
5 |
6 | # 功能
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/guide/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 快速开始 | 指南
3 | ---
4 |
5 | # 快速开始
6 |
7 |
8 |
9 | **usepy**是一个用于快速开发python项目的工具包,它提供了一些常用的工具函数,如`Dict`、`List`、`Decorator`等。
10 |
11 | ## 安装
12 |
13 | ::: code-group
14 |
15 | ```bash [pip]
16 | pip install usepy -U
17 | ```
18 | ```bash [poetry]
19 | poetry add usepy
20 | ```
21 | :::
22 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: home
3 | sidebar: false
4 |
5 | title: UsePy
6 | titleTemplate: A blazing easy python tool kit
7 |
8 | hero:
9 | name: UsePy
10 | text: 简单又便捷的Python工具包
11 | tagline: 你想要的方法,都在这里。use it!
12 | image:
13 | src: /logo-shadow.svg
14 | alt: UsePy
15 | actions:
16 | - theme: brand
17 | text: 快速开始
18 | link: /guide/
19 | - theme: alt
20 | text: 功能特性
21 | link: /guide/features
22 | - theme: alt
23 | text: View on PyPI
24 | link: https://pypi.org/project/usepy/
25 | - theme: alt
26 | text: View on GitHub
27 | link: https://github.com/mic1on/UsePy
28 |
29 | features:
30 | - title: 单测全覆盖
31 | details: 你use的方法,都有单测覆盖。
32 | - title: 0依赖
33 | details: 你use的方法,都是基于标准库。
34 | - title: 简单方便
35 | details: from usepy import *
36 | - title: 超多方法
37 | details: 50+实用方法
38 | ---
39 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "private": true,
4 | "scripts": {
5 | "dev": "vitepress --port 3333",
6 | "build": "nr prefetch && vitepress build",
7 | "build-no-prefetch": "vitepress build",
8 | "serve": "vitepress serve",
9 | "preview-https": "pnpm run build && serve .vitepress/dist",
10 | "preview-https-no-prefetch": "pnpm run build-no-prefetch && serve .vitepress/dist",
11 | "prefetch": "esno .vitepress/scripts/fetch-avatars.ts"
12 | },
13 | "dependencies": {
14 | "@vueuse/core": "^9.10.0",
15 | "jiti": "^1.16.1",
16 | "vue": "latest"
17 | },
18 | "devDependencies": {
19 | "@iconify-json/carbon": "^1.1.13",
20 | "@unocss/reset": "^0.48.3",
21 | "@vite-pwa/vitepress": "^0.0.4",
22 | "@vitejs/plugin-vue": "latest",
23 | "esno": "^0.16.3",
24 | "fast-glob": "^3.2.12",
25 | "fs-extra": "^10.1.0",
26 | "https-localhost": "^4.7.1",
27 | "unocss": "^0.48.3",
28 | "unplugin-vue-components": "^0.22.12",
29 | "vite": "^4.0.0",
30 | "vite-plugin-pwa": "^0.14.1",
31 | "vitepress": "1.0.0-alpha.76",
32 | "workbox-window": "^6.5.4"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/docs/public/CNAME:
--------------------------------------------------------------------------------
1 | usepy.code05.com
--------------------------------------------------------------------------------
/docs/public/_headers:
--------------------------------------------------------------------------------
1 | /
2 | X-Frame-Options: DENY
3 | X-XSS-Protection: 1; mode=block
4 |
5 | /api/
6 | X-Frame-Options: DENY
7 | X-XSS-Protection: 1; mode=block
8 |
9 | /config/
10 | X-Frame-Options: DENY
11 | X-XSS-Protection: 1; mode=block
12 |
13 | /guide/
14 | X-Frame-Options: DENY
15 | X-XSS-Protection: 1; mode=block
16 |
17 | /*.html
18 | X-Frame-Options: DENY
19 | X-XSS-Protection: 1; mode=block
20 |
21 | /*
22 | X-Content-Type-Options: nosniff
23 | Referrer-Policy: no-referrer
24 | Strict-Transport-Security: max-age=31536000; includeSubDomains
25 |
26 | /assets/*
27 | cache-control: max-age=31536000
28 | cache-control: immutable
29 |
--------------------------------------------------------------------------------
/docs/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/use-py/usepy/44e2436da3d95b3185a7669daa0af27674326a64/docs/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/docs/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/use-py/usepy/44e2436da3d95b3185a7669daa0af27674326a64/docs/public/favicon.ico
--------------------------------------------------------------------------------
/docs/public/logo-shadow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
45 |
--------------------------------------------------------------------------------
/docs/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/use-py/usepy/44e2436da3d95b3185a7669daa0af27674326a64/docs/public/logo.png
--------------------------------------------------------------------------------
/docs/public/og-original.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/use-py/usepy/44e2436da3d95b3185a7669daa0af27674326a64/docs/public/og-original.png
--------------------------------------------------------------------------------
/docs/public/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/use-py/usepy/44e2436da3d95b3185a7669daa0af27674326a64/docs/public/og.png
--------------------------------------------------------------------------------
/docs/public/pwa-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/use-py/usepy/44e2436da3d95b3185a7669daa0af27674326a64/docs/public/pwa-192x192.png
--------------------------------------------------------------------------------
/docs/public/pwa-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/use-py/usepy/44e2436da3d95b3185a7669daa0af27674326a64/docs/public/pwa-512x512.png
--------------------------------------------------------------------------------
/docs/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 |
--------------------------------------------------------------------------------
/docs/tags.md:
--------------------------------------------------------------------------------
1 | ---
2 | # layout: false
3 | title: TAGS
4 | ---
5 |
6 |
7 |
--------------------------------------------------------------------------------
/docs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "module": "esnext",
5 | "target": "esnext",
6 | "lib": ["DOM", "ESNext"],
7 | "strict": true,
8 | "jsx": "preserve",
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "noUnusedLocals": true,
14 | "strictNullChecks": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "types": [
17 | "vite/client",
18 | "vite-plugin-pwa/client",
19 | "vitepress"
20 | ],
21 | "paths": {
22 | "~/*": ["src/*"]
23 | }
24 | },
25 | "include": [
26 | "./*.ts",
27 | "./.vitepress/**/*.ts",
28 | "./.vitepress/**/*.vue"
29 | ],
30 | "exclude": ["dist", "node_modules"]
31 | }
32 |
--------------------------------------------------------------------------------
/docs/utils/bloom_filter.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | ::: info
6 |
7 | @Author: MicLon
8 | @Date: 2023/02/25
9 | @Description: 布隆过滤器
10 |
11 | :::
12 |
13 |
14 | ## 介绍
15 |
16 | 布隆过滤器是一种空间效率很高的随机数据结构,它可以用来告诉你,一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
17 |
18 | 参考:[布隆过滤器](https://www.cnblogs.com/cpselvis/p/6265825.html)
19 |
20 | ## 使用
21 |
22 | ```python{2}
23 | from redis import Redis
24 | from usepy import useBloomFilter
25 |
26 |
27 | rds = Redis(host='localhost', port=6379, db=0)
28 | bf = useBloomFilter(
29 | client=rds
30 | )
31 | bf.add('hello')
32 | bf.add('world')
33 | print(bf.exists('hello')) # True
34 | print(bf.exists('world')) # True
35 | print(bf.exists('python')) # False
36 | print(bf.exists('')) # False
37 | ```
38 |
--------------------------------------------------------------------------------
/docs/utils/is.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | ::: info
6 |
7 | @Author: MicLon
8 | @Date: 2023/02/19
9 | @Description: 用于检查数据类型的工具函数
10 |
11 | :::
12 |
13 |
14 |
15 | ## regexp
16 | 检查`value`是否是正则表达式
17 | ```python
18 | def regexp(value):
19 | """
20 | 检查`value`是否是正则表达式
21 | :param value: 要检查的值
22 | :return:
23 | """
24 | ...
25 |
26 | ```
27 | ## string
28 | 检查`value`是否是字符串
29 | ```python
30 | def string(value):
31 | """
32 | 检查`value`是否是字符串
33 | :param value: 要检查的值
34 | :return: bool
35 | """
36 | ...
37 |
38 | ```
39 | ## token
40 | 检查`value`是否符合token规范
41 | ```python
42 | def token(value):
43 | """
44 | 检查`value`是否符合token规范
45 | :param value: 要检查的值
46 | :return:
47 | """
48 | ...
49 |
50 | ```
51 |
--------------------------------------------------------------------------------
/docs/utils/timeout.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | ## useTimeoutFn
6 |
7 | 超时将会执行回调函数
8 |
9 | ```python
10 | def callback():
11 | print("timeout")
12 |
13 | timeout = useTimeoutFn(1, callback)
14 | timeout.start()
15 | # do something need 2 seconds
16 | timeout.stop()
17 |
18 | ```
19 |
20 |
21 | ```python
22 | def callback():
23 | print("timeout")
24 |
25 | # 无需`start`立即生效
26 | timeout = useTimeoutFn(1, callback, immediate = True)
27 | # timeout.start()
28 | # do something need 2 seconds
29 |
30 | ```
31 |
32 | ## useTimeout
33 |
34 | 超时
35 |
36 | ```python
37 |
38 | timeout = useTimeout(0.1)
39 | ```
40 |
--------------------------------------------------------------------------------
/docs/utils/timer.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | ## useTimer
6 |
7 | 定时器
8 |
9 | ```python
10 |
11 | # 每隔1秒执行一次
12 | timer = useTimer("test", lambda: print("test"), 1)
13 | timer.scheduler()
14 |
15 | # 取消定时器
16 | timer.cancel()
17 |
18 | ```
19 |
20 |
21 | ## useTimerManager
22 |
23 | 定时器管理器
24 |
25 | ```python
26 |
27 | timer1 = useTimer("test", lambda: print("test"), 1) # 每隔1秒执行一次
28 | timer2 = useTimer("test2", lambda: print("test2"), 2) # 每隔2秒执行一次
29 |
30 |
31 | timer_manager = useTimerManager(timer1, timer2)
32 |
33 | timer_manager.execute() # 执行定时器
34 | ```
35 |
--------------------------------------------------------------------------------
/docs/utils/to.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | ::: info
6 |
7 | @Author: MicLon
8 | @Date: 2023/02/19
9 | @Description: 用于转换数据类型的工具函数
10 |
11 | :::
12 |
13 |
14 |
15 | ## string
16 | 将任意数据转换为字符串
17 | ```python
18 | def string(data):
19 | """
20 | 将任意数据转换为字符串
21 | :param data: data
22 | :return: string
23 | """
24 | ...
25 |
26 | ```
27 |
28 |
29 | ## md5
30 | 将字符数据转换为md5
31 | ```python
32 | def md5(data: AnyStr) -> str:
33 | """
34 | 将字符数据转换为md5
35 | :param data: data
36 | :return: md5
37 | """
38 | ...
39 | ```
40 |
41 |
42 | ## sha1
43 | 将字符数据转换为sha1
44 | ```python
45 | def sha1(data: AnyStr) -> str:
46 | """
47 | 将字符数据转换为sha1
48 | :param data: data
49 | :return: sha1
50 | """
51 | ...
52 | ```
53 |
54 | ## camel
55 | 将字符串转换为驼峰命名
56 | ```python
57 | def camel(data: str, char: str = '-') -> str:
58 | """
59 | 将字符数据转换为驼峰命名
60 | :param data: data
61 | :param char: 特征字符,如:-、_
62 | :return:
63 | >>> camel("test")
64 | 'test'
65 | >>> camel("test-case")
66 | 'testCase'
67 | >>> camel("test_case", char="_")
68 | 'testCase'
69 | """
70 | ...
71 | ```
72 |
73 | ## snake
74 | 将字符串转换为下划线命名
75 | ```python
76 | def snake(data: str, char: str = '_') -> str:
77 | """
78 | 将字符数据转换为下划线命名
79 | :param data: data
80 | :param char: 特征字符,如:-、_
81 | :return:
82 | >>> snake("test")
83 | 'test'
84 | >>> snake("testCase")
85 | 'test_case'
86 | >>> snake("testCase", char="-")
87 | 'test-case'
88 | """
89 | ...
90 | ```
--------------------------------------------------------------------------------
/docs/utils/utils.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | ::: info
6 |
7 | @Author: MicLon
8 | @Date: 2023/02/19
9 | @Description: 一些其他工具集合
10 |
11 | :::
12 |
13 |
14 |
15 | ## cookie_to_dict
16 | 将字符串cookie转换为字典
17 | ```python
18 | def cookie_to_dict(cookies):
19 | """
20 | 将字符串cookie转换为字典
21 | :param cookies: cookie字符串
22 | :return: dict
23 | """
24 | ...
25 |
26 | ```
27 | ## data_to_dict
28 | 将字符串data转换为字典
29 | ```python
30 | def data_to_dict(data):
31 | """
32 | 将字符串data转换为字典
33 | :param data: data字符串。格式为`key1=value1&key2=value2`
34 | :return: dict
35 | """
36 | ...
37 |
38 | ```
39 | ## gen_unique_id
40 | 生成唯一id
41 | ```python
42 | def gen_unique_id():
43 | """
44 | 生成唯一id
45 | :return:
46 | """
47 | ...
48 |
49 | ```
50 | ## headers_to_dict
51 | 将字符串headers转换为字典
52 | ```python
53 | def headers_to_dict(headers):
54 | """
55 | 将字符串headers转换为字典
56 | :param headers: headers字符串
57 | :return: dict
58 | """
59 | ...
60 |
61 | ```
62 | ## uuid4
63 | Generate a random UUID.
64 | ```python
65 | def uuid4():
66 | """
67 | Generate a random UUID.
68 | """
69 | ...
70 |
71 | ```
72 |
73 | ## clean_html
74 |
75 | ```python
76 | def clean_html(html: str, white_tags=None) -> str:
77 | """
78 | 清除HTML标签
79 | >>> clean_html('This is a paragraph.
This is bold text.')
80 | 'This is a paragraph.This is bold text.'
81 | """
82 | ...
83 | ```
84 |
--------------------------------------------------------------------------------
/docs/vite.config.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import type { Plugin } from 'vite'
3 | import { defineConfig } from 'vite'
4 | import Components from 'unplugin-vue-components/vite'
5 | import Unocss from 'unocss/vite'
6 | import { presetAttributify, presetIcons, presetUno } from 'unocss'
7 | import { resolve } from 'path'
8 |
9 | export default defineConfig({
10 | optimizeDeps: {
11 | // vitepress is aliased with replacement `join(DIST_CLIENT_PATH, '/index')`
12 | // This needs to be excluded from optimization
13 | exclude: ['vitepress'],
14 | },
15 | plugins: [
16 | // TODO remove cast when moved to Vite 3
17 | Components({
18 | include: [/\.vue/, /\.md/],
19 | dirs: '.vitepress/components',
20 | dts: '.vitepress/components.d.ts',
21 | }) as Plugin,
22 | Unocss({
23 | shortcuts: [
24 | ['btn', 'px-4 py-1 rounded inline-flex justify-center gap-2 text-white leading-30px children:mya !no-underline cursor-pointer disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50'],
25 | ],
26 | presets: [
27 | presetUno({
28 | dark: 'media',
29 | }),
30 | presetAttributify(),
31 | presetIcons({
32 | scale: 1.2,
33 | }),
34 | ],
35 | }) as unknown as Plugin,
36 | IncludesPlugin(),
37 | ],
38 | })
39 |
40 | function IncludesPlugin(): Plugin {
41 | return {
42 | name: 'include-plugin',
43 | enforce: 'pre',
44 | transform(code, id) {
45 | let changed = false
46 | code = code.replace(/\[@@include\]\((.*?)\)/, (_, url) => {
47 | changed = true
48 | const full = resolve(id, url)
49 | return fs.readFileSync(full, 'utf-8')
50 | })
51 | if (changed)
52 | return code
53 | },
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/example/example.py:
--------------------------------------------------------------------------------
1 | from usepy import chunk
2 | from usepy.list import chunk as chunk_list
3 |
4 | print(chunk([1, 2, 3, 4, 5], 2))
5 | print(chunk_list([1, 2, 3, 4, 5], 2))
6 |
7 |
8 | from usepy import AdDict
9 |
10 | d = AdDict({"a": 1, "b": 2, "c": {"d": 3, "e": 4}})
11 | print(d.a)
12 | print(d.c.e)
13 |
14 |
15 | from usepy import camel_case
16 |
17 | print(camel_case("hello_world"))
18 |
--------------------------------------------------------------------------------
/logo-shadow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
45 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "usepy"
3 | version = "0.4.0"
4 | description = "usepy"
5 | homepage = "https://usepy.code05.com/"
6 | authors = ["miclon "]
7 | readme = "README.md"
8 | packages = [
9 | { include = 'usepy', from = 'src' }
10 | ]
11 |
12 | [tool.poetry.dependencies]
13 | python = "^3.7"
14 | typing-extensions = [
15 | { version = "^4.0.0", python = ">=3.6,<3.7" },
16 | { version = "^4.5.0", python = ">=3.7" }
17 | ]
18 |
19 |
20 | [tool.poetry.group.test.dependencies]
21 | pylint = "*"
22 | pytest = "*"
23 | black = "*"
24 | flake8 = "*"
25 | isort = "*"
26 | ruff = "*"
27 | pre-commit = "*"
28 | pre-commit-hooks = "*"
29 |
30 | [tool.ruff]
31 | ignore = [
32 | "E501", # line too long, handled by black
33 | ]
34 |
35 | [build-system]
36 | requires = ["poetry-core"]
37 | build-backend = "poetry.core.masonry.api"
38 |
--------------------------------------------------------------------------------
/resources/overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/use-py/usepy/44e2436da3d95b3185a7669daa0af27674326a64/resources/overview.png
--------------------------------------------------------------------------------
/src/usepy/__init__.py:
--------------------------------------------------------------------------------
1 | from .list import *
2 | from .dict import *
3 | from .string import *
4 | from .decorator import *
5 | from .validator import *
6 | from .converter import *
7 | from .date import *
8 | from .misc import *
9 |
10 | __all__ = [
11 | # list
12 | "chunk",
13 | "count_by",
14 | "compact",
15 | "difference",
16 | "flatten",
17 | "flatten_deep",
18 | "every",
19 | "some",
20 | "sample",
21 | "shuffle",
22 | "without",
23 | "uniq",
24 | "union",
25 | "key_by",
26 | "zip_",
27 | "zip_dict",
28 | "first",
29 | "last",
30 | # dict
31 | "AdDict",
32 | "sort_by_key",
33 | "sort_by_value",
34 | "merge_dicts",
35 | # string
36 | "camel_case",
37 | "capitalize",
38 | "kebab_case",
39 | "left",
40 | "lower_case",
41 | "middle",
42 | "middle_batch",
43 | "pascal_case",
44 | "right",
45 | "snake_case",
46 | # decorator
47 | "retry",
48 | "catch_error",
49 | "singleton",
50 | "throttle",
51 | # validator
52 | "is_url",
53 | "is_async_function",
54 | # converter
55 | "to_list",
56 | "to_md5",
57 | "to_string",
58 | "to_set",
59 | "to_bool",
60 | # date
61 | "parse",
62 | "format",
63 | "now",
64 | "timestamp",
65 | # misc
66 | "dynamic_import",
67 | "get_function_name",
68 | ]
69 |
--------------------------------------------------------------------------------
/src/usepy/converter/__init__.py:
--------------------------------------------------------------------------------
1 | from .to_list import to_list
2 | from .to_md5 import to_md5
3 | from .to_string import to_string
4 | from .to_set import to_set
5 | from .to_bool import to_bool
6 |
7 |
8 | __all__ = [
9 | "to_list",
10 | "to_md5",
11 | "to_string",
12 | "to_set",
13 | "to_bool",
14 | ]
15 |
--------------------------------------------------------------------------------
/src/usepy/converter/to_bool.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 |
4 | def to_bool(value: Any) -> bool:
5 | """Convert a value to a boolean.
6 |
7 | Args:
8 | value (Any): The value to convert.
9 |
10 | Returns:
11 | bool: The boolean value.
12 |
13 | Examples:
14 | >>> to_bool(True)
15 | True
16 | >>> to_bool("True")
17 | True
18 | >>> to_bool("true")
19 | True
20 | >>> to_bool("t")
21 | True
22 | """
23 | if isinstance(value, bool):
24 | return value
25 | if isinstance(value, str):
26 | return value.lower() in ("yes", "true", "t", "y", "1")
27 | return bool(value)
28 |
--------------------------------------------------------------------------------
/src/usepy/converter/to_list.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 |
4 | def to_list(value: Any) -> list:
5 | """Convert a value to a list.
6 |
7 | Args:
8 | value (Any): The value to convert.
9 |
10 | Returns:
11 | list: The list value.
12 | """
13 | if isinstance(value, list):
14 | return value
15 | if isinstance(value, (str, bytes)):
16 | return list(value)
17 | try:
18 | return list(value)
19 | except TypeError:
20 | return [value]
21 |
--------------------------------------------------------------------------------
/src/usepy/converter/to_md5.py:
--------------------------------------------------------------------------------
1 | from typing import AnyStr
2 | from .to_string import to_string
3 |
4 |
5 | def to_md5(data: AnyStr) -> str:
6 | """Convert a value to a md5.
7 |
8 | Args:
9 | value (AnyStr): The value to convert.
10 |
11 | Returns:
12 | str: The md5 value.
13 |
14 | Examples:
15 | >>> to_md5("hello")
16 | '5d41402abc4b2a76b9719d911017c592'
17 | """
18 | import hashlib
19 |
20 | return hashlib.md5(to_string(data).encode()).hexdigest()
21 |
--------------------------------------------------------------------------------
/src/usepy/converter/to_set.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 | from .to_list import to_list
3 |
4 |
5 | def to_set(value: Any) -> set:
6 | """Convert a value to a set.
7 |
8 | Args:
9 | value (Any): The value to convert.
10 |
11 | Returns:
12 | set: The set value.
13 | """
14 | if isinstance(value, set):
15 | return value
16 | return set(to_list(value))
17 |
--------------------------------------------------------------------------------
/src/usepy/converter/to_string.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 |
4 | def to_string(value: Any) -> str:
5 | """Convert a value to a string.
6 |
7 | Args:
8 | value (Any): The value to convert.
9 |
10 | Returns:
11 | str: The string value.
12 | """
13 | if value is None:
14 | return ""
15 | if isinstance(value, (list, tuple, set)):
16 | return ", ".join(map(to_string, value))
17 | if isinstance(value, dict):
18 | return ", ".join(f"{k}: {to_string(v)}" for k, v in value.items())
19 | return str(value)
20 |
--------------------------------------------------------------------------------
/src/usepy/date/__init__.py:
--------------------------------------------------------------------------------
1 | from .parse import parse
2 | from .format import format
3 | from .now import now
4 | from .timestamp import timestamp
5 |
6 | __all__ = [
7 | "parse",
8 | "format",
9 | "now",
10 | "timestamp",
11 | ]
12 |
--------------------------------------------------------------------------------
/src/usepy/date/format.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 |
4 | def format(dt: datetime, fmt=None) -> str:
5 | """format date
6 |
7 | Args:
8 | dt (datetime): The datetime to format.
9 | fmt (str, optional): The format of the datetime. Defaults to None.
10 | """
11 | _fmt = fmt or "%Y-%m-%d %H:%M:%S"
12 | return dt.strftime(_fmt)
13 |
--------------------------------------------------------------------------------
/src/usepy/date/now.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 |
4 | def now() -> datetime:
5 | """get current datetime
6 |
7 | Returns:
8 | datetime: current datetime
9 | """
10 | return datetime.now()
11 |
--------------------------------------------------------------------------------
/src/usepy/date/parse.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from itertools import chain
3 |
4 |
5 | def parse(time_str: str, fmt=None) -> datetime:
6 | """parse date
7 |
8 | Args:
9 | time_str (str): The time string to parse.
10 | fmt (str, optional): The format of the time string. Defaults to None.
11 |
12 | Returns:
13 | datetime: The parsed datetime.
14 | """
15 | DATETIME_COMMON = (
16 | "%Y-%m-%d %H:%M:%S",
17 | "%Y-%m-%d %H:%M",
18 | "%Y/%m/%d %H:%M:%S",
19 | "%Y/%m/%d %H:%M",
20 | "%Y年%m月%d日%H:%M:%S",
21 | "%Y年%m月%d日 %H:%M:%S",
22 | "%Y年%m月%d日%H时%M分%S秒",
23 | "%Y年%m月%d日 %H时%M分%S秒",
24 | )
25 | DATE_FORMATS = (
26 | "%Y-%m-%d",
27 | "%Y%m%d",
28 | "%Y/%m/%d",
29 | "%Y.%m.%d",
30 | "%d.%m.%y",
31 | "%d.%m.%Y",
32 | "%Y %m %d",
33 | "%m/%d/%Y",
34 | )
35 | DATETIME_FORMATS = list(
36 | chain.from_iterable(
37 | [
38 | ["{} %H:%M:%S".format(fmt) for fmt in DATE_FORMATS],
39 | ["{} %H:%M".format(fmt) for fmt in DATE_FORMATS],
40 | ["{}T%H:%M:%S.%f%z".format(fmt) for fmt in DATE_FORMATS],
41 | ]
42 | )
43 | )
44 | s = time_str.strip()
45 | if fmt is not None:
46 | return datetime.strptime(s, fmt)
47 | for fmt in chain.from_iterable((DATETIME_COMMON, DATETIME_FORMATS, DATE_FORMATS)):
48 | try:
49 | return datetime.strptime(s, fmt)
50 | except ValueError:
51 | continue
52 | raise ValueError(f"No valid date format found for '{s}'")
53 |
--------------------------------------------------------------------------------
/src/usepy/date/timestamp.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from typing import Optional
3 |
4 |
5 | def timestamp(dt: Optional[datetime] = None, digits: Optional[int] = 10) -> int:
6 | """
7 | get timestamp from datetime object
8 |
9 | :param dt: datetime object, default to current time
10 | :param digits: timestamp digits, 10 or 13, default to 10
11 | :return: integer timestamp
12 | """
13 | if dt is None:
14 | dt = datetime.now()
15 |
16 | if digits == 10:
17 | return int(dt.timestamp())
18 | elif digits == 13:
19 | return int(dt.timestamp() * 1000)
20 | else:
21 | raise ValueError("timestamp digits must be 10 or 13")
22 |
--------------------------------------------------------------------------------
/src/usepy/decorator/__init__.py:
--------------------------------------------------------------------------------
1 | from .retry import Retry as retry
2 | from .catch_error import catch_error
3 | from .singleton import singleton
4 | from .throttle import Throttle as throttle
5 |
6 | __all__ = ["retry", "catch_error", "singleton", "throttle"]
7 |
--------------------------------------------------------------------------------
/src/usepy/decorator/catch_error.py:
--------------------------------------------------------------------------------
1 | import functools
2 | import logging
3 | from typing import Callable, Any, Optional, TypeVar
4 |
5 | T = TypeVar("T")
6 |
7 |
8 | def catch_error(
9 | return_val: Optional[T] = None,
10 | ) -> Callable[[Callable[..., T]], Callable[..., Optional[T]]]:
11 | """
12 | Catch Error Decorator
13 |
14 | This decorator function is used to catch exceptions that may occur during the execution of a function.
15 | If an exception occurs, it will log the exception using the `logging.debug` method and return the specified `return_val`.
16 |
17 | Args:
18 | return_val (Optional[T]): The value to be returned if an exception occurs during the execution of the decorated function.
19 | If not provided, it defaults to `None`.
20 |
21 | Returns:
22 | Callable[[Callable[..., T]], Callable[..., Optional[T]]]: A decorator function that takes a function as input
23 | and returns a new function that catches exceptions.
24 |
25 | Example:
26 | >>> import logging
27 | >>> logging.basicConfig(level=logging.DEBUG)
28 | >>>
29 | >>> @catch_error(return_val=0)
30 | ... def divide(a, b):
31 | ... return a / b
32 | ...
33 | >>> divide(10, 2)
34 | 5.0
35 | >>> divide(10, 0)
36 | Error occurred: division by zero
37 | 0
38 | """
39 |
40 | def decorator(func: Callable[..., T]) -> Callable[..., Optional[T]]:
41 | @functools.wraps(func)
42 | def wrapper(*args: Any, **kwargs: Any) -> Optional[T]:
43 | try:
44 | return func(*args, **kwargs)
45 | except Exception as e:
46 | logging.debug(f"Error occurred: {e}")
47 | return return_val
48 |
49 | return wrapper
50 |
51 | return decorator
52 |
--------------------------------------------------------------------------------
/src/usepy/decorator/retry.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import functools
3 | import time
4 |
5 |
6 | class MaxRetryError(Exception):
7 | pass
8 |
9 |
10 | class Retry(object):
11 | """
12 | Decorator for retrying a function, supports asynchronous functions
13 |
14 | :param max_attempts: maximum retry attempts
15 | :param retry_interval: retry interval
16 | :param retry_exceptions: retry exceptions
17 |
18 | >>> @retry(max_attempts=3, retry_interval=1)
19 | ... def test():
20 | ... print('test')
21 | ... raise Exception('test')
22 | """
23 |
24 | def __init__(self, max_attempts=3, retry_interval=1, retry_exceptions=None):
25 | self.max_attempts = max_attempts
26 | self.retry_interval = retry_interval
27 | self.retry_exceptions = retry_exceptions or (Exception,)
28 |
29 | def __call__(self, func):
30 | @functools.wraps(func)
31 | def wrapper(*args, **kwargs):
32 | retry = 0
33 | while retry < self.max_attempts:
34 | try:
35 | return func(*args, **kwargs)
36 | except self.retry_exceptions as e:
37 | retry += 1
38 | if retry >= self.max_attempts:
39 | raise MaxRetryError(
40 | f"Max retry {self.max_attempts} times,Error reason: {e}"
41 | )
42 | time.sleep(self.retry_interval)
43 |
44 | @functools.wraps(func)
45 | async def async_wrapper(*args, **kwargs):
46 | retry = 0
47 | while retry < self.max_attempts:
48 | try:
49 | return await func(*args, **kwargs)
50 | except self.retry_exceptions as e:
51 | retry += 1
52 | if retry >= self.max_attempts:
53 | raise MaxRetryError(
54 | f"Max retry {self.max_attempts} times,Error reason: {e}"
55 | )
56 | await asyncio.sleep(self.retry_interval)
57 |
58 | wrapper_func = async_wrapper if asyncio.iscoroutinefunction(func) else wrapper
59 | return wrapper_func
60 |
--------------------------------------------------------------------------------
/src/usepy/decorator/singleton.py:
--------------------------------------------------------------------------------
1 | import functools
2 | import threading
3 | from typing import Any, Callable, Type, TypeVar
4 |
5 | T = TypeVar("T")
6 |
7 |
8 | def singleton(cls: Type[T]) -> Callable[..., T]:
9 | """
10 | A decorator for implementing the Singleton pattern.
11 |
12 | This decorator ensures that only one instance of the decorated class is created,
13 | and subsequent calls to the class constructor return the same instance.
14 |
15 | Args:
16 | cls (Type[T]): The class to be decorated.
17 |
18 | Returns:
19 | Callable[..., T]: A function that returns the singleton instance of the class.
20 |
21 | Example:
22 | >>> @singleton
23 | ... class MyClass:
24 | ... def __init__(self, value):
25 | ... self.value = value
26 | ...
27 | >>> instance1 = MyClass(42)
28 | >>> instance2 = MyClass(24)
29 | >>> print(instance1.value)
30 | 42
31 | >>> print(instance1 is instance2)
32 | True
33 | """
34 | _instances: dict[Type[T], T] = {}
35 | _lock = threading.Lock()
36 |
37 | @functools.wraps(cls)
38 | def _singleton(*args: Any, **kwargs: Any) -> T:
39 | if cls not in _instances:
40 | with _lock:
41 | if cls not in _instances:
42 | _instances[cls] = cls(*args, **kwargs)
43 | return _instances[cls]
44 |
45 | return _singleton
46 |
--------------------------------------------------------------------------------
/src/usepy/decorator/throttle.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from functools import wraps
3 | import time
4 |
5 |
6 | class Throttle:
7 | """Throttle Decorator
8 |
9 | This decorator is used to limit the rate at which a function can be called.
10 | It ensures that the function is not called more than once in a given time period.
11 |
12 | Args:
13 | delay (int): The time period in seconds within which the function can be called.
14 | """
15 |
16 | def __init__(self, delay: int):
17 | self.delay = delay
18 | self.last_called = 0
19 |
20 | def __call__(self, func):
21 | @wraps(func)
22 | def wrapper(*args, **kwargs):
23 | nonlocal self
24 | current_time = time.time()
25 | if current_time - self.last_called >= self.delay:
26 | result = func(*args, **kwargs)
27 | self.last_called = current_time
28 | return result
29 |
30 | @wraps(func)
31 | async def async_wrapper(*args, **kwargs):
32 | return wrapper(*args, **kwargs)
33 |
34 | wrapper_func = async_wrapper if asyncio.iscoroutinefunction(func) else wrapper
35 |
36 | return wrapper_func
37 |
--------------------------------------------------------------------------------
/src/usepy/dict/__init__.py:
--------------------------------------------------------------------------------
1 | from .ad_dict import AdDict
2 | from .sort_by_key import sort_by_key
3 | from .sort_by_value import sort_by_value
4 | from .merge_dicts import merge_dicts
5 |
6 | __all__ = [
7 | "AdDict",
8 | "sort_by_key",
9 | "sort_by_value",
10 | "merge_dicts",
11 | ]
12 |
--------------------------------------------------------------------------------
/src/usepy/dict/ad_dict.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | update from https://github.com/mewwts/addict
4 | """
5 | import copy
6 |
7 |
8 | class AdDict(dict):
9 | def __init__(__self, *args, **kwargs):
10 | super().__init__()
11 | object.__setattr__(__self, "__parent", kwargs.pop("__parent", None))
12 | object.__setattr__(__self, "__auto_convert", kwargs.pop("auto_convert", False))
13 | object.__setattr__(__self, "__key", kwargs.pop("__key", None))
14 | object.__setattr__(__self, "__frozen", False)
15 | for arg in args:
16 | if not arg:
17 | continue
18 | elif isinstance(arg, dict):
19 | for key, val in arg.items():
20 | __self[key] = __self._hook(val)
21 | elif isinstance(arg, tuple) and (not isinstance(arg[0], tuple)):
22 | __self[arg[0]] = __self._hook(arg[1])
23 | else:
24 | for key, val in iter(arg):
25 | __self[key] = __self._hook(val)
26 |
27 | for key, val in kwargs.items():
28 | __self[key] = __self._hook(val)
29 |
30 | def __setattr__(self, name, value):
31 | if hasattr(self.__class__, name):
32 | raise AttributeError(
33 | "'AdDict' object attribute " "'{0}' is read-only".format(name)
34 | )
35 | else:
36 | self[name] = value
37 |
38 | def __setitem__(self, name, value):
39 | is_frozen = hasattr(self, "__frozen") and object.__getattribute__(
40 | self, "__frozen"
41 | )
42 | if is_frozen and name not in super(AdDict, self).keys():
43 | raise KeyError(name)
44 | is_auto_convert = hasattr(self, "__auto_convert") and object.__getattribute__(
45 | self, "__auto_convert"
46 | )
47 | if (
48 | is_auto_convert
49 | and isinstance(value, dict)
50 | and not isinstance(value, AdDict)
51 | ):
52 | value = self._hook(value)
53 | super(AdDict, self).__setitem__(name, value)
54 | try:
55 | p = object.__getattribute__(self, "__parent")
56 | key = object.__getattribute__(self, "__key")
57 | except AttributeError:
58 | p = None
59 | key = None
60 | if p is not None:
61 | p[key] = self
62 | object.__delattr__(self, "__parent")
63 | object.__delattr__(self, "__key")
64 |
65 | def __add__(self, other):
66 | if not self.keys():
67 | return other
68 | else:
69 | self_type = type(self).__name__
70 | other_type = type(other).__name__
71 | msg = "unsupported operand type(s) for +: '{}' and '{}'"
72 | raise TypeError(msg.format(self_type, other_type))
73 |
74 | @classmethod
75 | def _hook(cls, item):
76 | if isinstance(item, dict):
77 | return cls(item)
78 | elif isinstance(item, (list, tuple)):
79 | return type(item)(cls._hook(elem) for elem in item)
80 | return item
81 |
82 | def __getattr__(self, item):
83 | return self.__getitem__(item)
84 |
85 | def __missing__(self, name):
86 | if object.__getattribute__(self, "__frozen"):
87 | raise KeyError(name)
88 | return self.__class__(__parent=self, __key=name)
89 |
90 | def __delattr__(self, name):
91 | del self[name]
92 |
93 | def to_dict(self):
94 | base = {}
95 | for key, value in self.items():
96 | if isinstance(value, type(self)):
97 | base[key] = value.to_dict()
98 | elif isinstance(value, (list, tuple)):
99 | base[key] = type(value)(
100 | item.to_dict() if isinstance(item, type(self)) else item
101 | for item in value
102 | )
103 | else:
104 | base[key] = value
105 | return base
106 |
107 | def copy(self):
108 | return copy.copy(self)
109 |
110 | def deepcopy(self):
111 | return copy.deepcopy(self)
112 |
113 | def __deepcopy__(self, memo):
114 | other = self.__class__()
115 | memo[id(self)] = other
116 | for key, value in self.items():
117 | other[copy.deepcopy(key, memo)] = copy.deepcopy(value, memo)
118 | return other
119 |
120 | def update(self, *args, **kwargs):
121 | other = {}
122 | if args:
123 | if len(args) > 1:
124 | raise TypeError()
125 | other.update(args[0])
126 | other.update(kwargs)
127 | for k, v in other.items():
128 | if (
129 | (k not in self)
130 | or (not isinstance(self[k], dict))
131 | or (not isinstance(v, dict))
132 | ):
133 | self[k] = v
134 | else:
135 | self[k].update(v)
136 |
137 | def __getnewargs__(self):
138 | return tuple(self.items())
139 |
140 | def __getstate__(self):
141 | return self
142 |
143 | def __setstate__(self, state):
144 | self.update(state)
145 |
146 | def __or__(self, other):
147 | if not isinstance(other, (AdDict, dict)):
148 | return NotImplemented
149 | new = AdDict(self)
150 | new.update(other)
151 | return new
152 |
153 | def __ror__(self, other):
154 | if not isinstance(other, (AdDict, dict)):
155 | return NotImplemented
156 | new = AdDict(other)
157 | new.update(self)
158 | return new
159 |
160 | def __ior__(self, other):
161 | self.update(other)
162 | return self
163 |
164 | def setdefault(self, key, default=None):
165 | if key in self:
166 | return self[key]
167 | else:
168 | self[key] = default
169 | return default
170 |
171 | def freeze(self, shouldFreeze=True):
172 | object.__setattr__(self, "__frozen", shouldFreeze)
173 | for key, val in self.items():
174 | if isinstance(val, AdDict):
175 | val.freeze(shouldFreeze)
176 |
177 | def unfreeze(self):
178 | self.freeze(False)
179 |
--------------------------------------------------------------------------------
/src/usepy/dict/merge_dicts.py:
--------------------------------------------------------------------------------
1 | from typing import Dict
2 |
3 |
4 | def merge_dicts(*dicts: Dict) -> Dict:
5 | """merge dicts
6 |
7 | Args:
8 | dicts (Dict): The dicts to merge.
9 |
10 | Returns:
11 | Dict: The merged dict.
12 |
13 | Examples:
14 | >>> merge_dicts({'a': 1, 'b': 2}, {'b': 3, 'c': 4})
15 | {'a': 1, 'b': 3, 'c': 4}
16 | """
17 | result = {}
18 | for d in dicts:
19 | result.update(d)
20 | return result
21 |
--------------------------------------------------------------------------------
/src/usepy/dict/sort_by_key.py:
--------------------------------------------------------------------------------
1 | from typing import Dict
2 |
3 |
4 | def sort_by_key(original_dict: Dict, az: bool = True) -> Dict:
5 | """sort dict by key
6 |
7 | Args:
8 | original_dict (Dict): The original dictionary.
9 | az (bool): Whether to sort in ascending order. Defaults to True.
10 |
11 | Returns:
12 | Dict: The sorted dictionary.
13 |
14 | Examples:
15 | >>> sort_by_key({'c': 1, 'b': 2, 'a': 3})
16 | {'a': 3, 'b': 2, 'c': 1}
17 | """
18 | return dict(sorted(original_dict.items(), reverse=not az))
19 |
--------------------------------------------------------------------------------
/src/usepy/dict/sort_by_value.py:
--------------------------------------------------------------------------------
1 | from typing import Dict
2 |
3 |
4 | def sort_by_value(original_dict: Dict, az: bool = True) -> Dict:
5 | """sort dict by value
6 |
7 | Args:
8 | original_dict (Dict): The original dictionary.
9 | az (bool): Whether to sort in ascending order. Defaults to True.
10 |
11 | Returns:
12 | Dict: The sorted dictionary.
13 |
14 | Examples:
15 | >>> sort_by_value({"a": 3, "b": 2, "c": 1}, False)
16 | {'c': 1, 'b': 2, 'a': 3}
17 | """
18 | return dict(sorted(original_dict.items(), key=lambda x: x[1], reverse=not az))
19 |
--------------------------------------------------------------------------------
/src/usepy/list/__init__.py:
--------------------------------------------------------------------------------
1 | from .chunk import chunk
2 | from .count_by import count_by
3 | from .compact import compact
4 | from .difference import difference
5 | from .flatten import flatten
6 | from .flatten_deep import flatten_deep
7 | from .every import every
8 | from .some import some
9 | from .sample import sample
10 | from .shuffle import shuffle
11 | from .without import without
12 | from .uniq import uniq
13 | from .union import union
14 | from .key_by import key_by
15 | from .zip_tuple import zip_tuple
16 | from .zip_dict import zip_dict
17 | from .first import first
18 | from .last import last
19 |
20 |
21 | __all__ = [
22 | "chunk",
23 | "count_by",
24 | "compact",
25 | "difference",
26 | "flatten",
27 | "flatten_deep",
28 | "every",
29 | "some",
30 | "sample",
31 | "shuffle",
32 | "without",
33 | "uniq",
34 | "union",
35 | "key_by",
36 | "zip_tuple",
37 | "zip_dict",
38 | "first",
39 | "last",
40 | ]
41 |
--------------------------------------------------------------------------------
/src/usepy/list/chunk.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar, Sequence, List
2 |
3 | T = TypeVar('T')
4 |
5 |
6 | def chunk(arr: Sequence[T], size: int) -> List[List[T]]:
7 | """
8 | Splits a sequence into smaller sequences of a specified length.
9 |
10 | This function takes an input sequence and divides it into multiple smaller sequences,
11 | each of a specified length. If the input sequence cannot be evenly divided,
12 | the final sub-sequence will contain the remaining elements.
13 |
14 | Args:
15 | arr (Sequence[T]): The sequence to be chunked into smaller sequences.
16 | size (int): The size of each smaller sequence. Must be a positive integer.
17 |
18 | Returns:
19 | List[List[T]]: A two-dimensional list where each sub-list has a maximum length of `size`.
20 |
21 | Raises:
22 | ValueError: If `size` is not a positive integer.
23 |
24 | Examples:
25 | >>> chunk([1, 2, 3, 4, 5], 2)
26 | [[1, 2], [3, 4], [5]]
27 |
28 | >>> chunk(['a', 'b', 'c', 'd', 'e', 'f', 'g'], 3)
29 | [['a', 'b', 'c'], ['d', 'e', 'f'], ['g']]
30 | """
31 | if not isinstance(size, int) or size <= 0:
32 | raise ValueError('Size must be an integer greater than zero.')
33 |
34 | chunk_length = (len(arr) + size - 1) // size
35 | result: List[List[T]] = [[] for _ in range(chunk_length)]
36 |
37 | for i, item in enumerate(arr):
38 | result[i // size].append(item)
39 |
40 | return result
41 |
--------------------------------------------------------------------------------
/src/usepy/list/compact.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar, Sequence
2 |
3 | T = TypeVar("T")
4 |
5 |
6 | def compact(lst: Sequence[T]) -> Sequence[T]:
7 | """
8 | Removes falsey values (False, None, 0, '', [], {}) from a sequence.
9 |
10 | Args:
11 | lst (Sequence[T]): The input sequence to remove falsey values.
12 |
13 | Returns:
14 | Sequence[T]: A new sequence with all falsey values removed.
15 |
16 | Example:
17 | >>> compact([0, 1, False, 2, '', 3, None, 4, {}, 5])
18 | [1, 2, 3, 4, 5]
19 | """
20 | return [item for item in lst if item]
21 |
--------------------------------------------------------------------------------
/src/usepy/list/count_by.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar, Sequence, Callable, Dict
2 |
3 | T = TypeVar('T')
4 |
5 |
6 | def count_by(arr: Sequence[T], mapper: Callable[[T], str]) -> Dict[str, int]:
7 | """
8 | Count the occurrences of each item in a sequence
9 | based on a transformation function.
10 |
11 | This function takes a sequence and a transformation function
12 | that converts each item in the sequence to a string. It then
13 | counts the occurrences of each transformed item and returns
14 | a dictionary with the transformed items as keys and the counts
15 | as values.
16 |
17 | Args:
18 | arr (Sequence[T]): The input sequence to count occurrences.
19 | mapper (Callable[[T], str]): The transformation function that maps each item to a string key.
20 |
21 | Returns:
22 | Dict[str, int]: A dictionary containing the transformed items as keys and the
23 | counts as values.
24 | """
25 | result: Dict[str, int] = {}
26 |
27 | for item in arr:
28 | key = mapper(item)
29 | result[key] = result.get(key, 0) + 1
30 |
31 | return result
32 |
--------------------------------------------------------------------------------
/src/usepy/list/difference.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar, Sequence, Set
2 |
3 | T = TypeVar('T')
4 |
5 |
6 | def difference(first_lst: Sequence[T], second_lst: Sequence[T]) -> list[T]:
7 | """
8 | Computes the difference between two sequences.
9 |
10 | This function takes two sequences and returns a new sequence containing the elements
11 | that are present in the first sequence but not in the second sequence. It effectively
12 | filters out any elements from the first sequence that also appear in the second sequence.
13 |
14 | Args:
15 | first_lst (Sequence[T]): The sequence from which to derive the difference. This is the primary sequence
16 | from which elements will be compared and filtered.
17 | second_lst (Sequence[T]): The sequence containing elements to be excluded from the first sequence.
18 | Each element in this sequence will be checked against the first sequence, and if a match is found,
19 | that element will be excluded from the result.
20 |
21 | Returns:
22 | list[T]: A new sequence containing the elements that are present in the first sequence but not
23 | in the second sequence.
24 |
25 | Example:
26 | >>> array1 = [1, 2, 3, 4, 5]
27 | >>> array2 = [2, 4]
28 | >>> result = difference(array1,array2)
29 | >>> result
30 | [1, 3, 5]
31 | """
32 | second_set: Set[T] = set(second_lst)
33 | return [item for item in first_lst if item not in second_set]
34 |
--------------------------------------------------------------------------------
/src/usepy/list/every.py:
--------------------------------------------------------------------------------
1 | from typing import List, Callable, TypeVar
2 | from functools import reduce
3 | from operator import and_
4 |
5 | T = TypeVar('T')
6 |
7 |
8 | def every(array: List[T], fn: Callable[[T], bool]) -> bool:
9 | """
10 | Checks if every element in the given array satisfies the provided condition function.
11 |
12 | Args:
13 | array (List[T]): The input array to be checked.
14 | fn (Callable[[T], bool]): The condition function that takes an element of the array as input
15 | and returns a boolean value indicating whether the element satisfies the condition or not.
16 |
17 | Returns:
18 | bool: True if every element in the array satisfies the condition function, False otherwise.
19 |
20 | Example:
21 | >>> is_even = lambda x: x % 2 == 0
22 | >>> every([2, 4, 6, 8], is_even)
23 | True
24 | >>> every([1, 3, 5, 7], is_even)
25 | False
26 | >>> every([],lambda x: x > 0)
27 | True
28 | """
29 | return reduce(and_, [fn(element) for element in array], True)
30 |
--------------------------------------------------------------------------------
/src/usepy/list/first.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar, Sequence
2 |
3 | T = TypeVar("T")
4 |
5 |
6 | def first(array: Sequence[T]) -> T | None:
7 | """
8 | Gets the first element of `array`.
9 |
10 | :param array: The array to query.
11 | :type array: Sequence[T]
12 | :return: Returns the first element of `array`.
13 | :rtype: T | None
14 |
15 | :Example:
16 |
17 | >>> first([1, 2, 3])
18 | 1
19 | """
20 | return array[0] if array else None
21 |
--------------------------------------------------------------------------------
/src/usepy/list/flatten.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar, List, Union, Optional
2 |
3 | T = TypeVar("T")
4 | D = TypeVar("D", int, float)
5 |
6 |
7 | def flatten(lst: List[T], depth: D = 1) -> List[Union[T, List[T]]]:
8 | """
9 | Flattens an array up to the specified depth.
10 |
11 | Args:
12 | lst (List[T]): The array to flatten.
13 | depth (Optional[D], optional): The depth level specifying how deep a nested array structure should be flattened.
14 | If `float('inf')`, the array will be flattened completely. Defaults to 1.
15 |
16 | Returns:
17 | List[Union[T, List[T]]]: A new array that has been flattened.
18 |
19 | Examples:
20 | >>> flatten([1, [2, 3], [4, [5, 6]]],1)
21 | [1, 2, 3, 4, [5, 6]]
22 |
23 | >>> flatten([1, [2, 3], [4, [5, 6]]],2)
24 | [1, 2, 3, 4, 5, 6]
25 |
26 | >>> flatten([1, [2, [3, [4, [5]]]]], float('inf'))
27 | [1, 2, 3, 4, 5]
28 | """
29 | result: List[Union[T, List[T]]] = []
30 | floored_depth = depth
31 |
32 | def recursive(arr: List[T], current_depth: int) -> None:
33 | for item in arr:
34 | if isinstance(item, list) and current_depth < floored_depth:
35 | recursive(item, current_depth + 1)
36 | else:
37 | result.append(item)
38 |
39 | recursive(lst, 0)
40 | return result
41 |
--------------------------------------------------------------------------------
/src/usepy/list/flatten_deep.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar
2 |
3 | from usepy.list.flatten import flatten
4 |
5 | T = TypeVar("T")
6 |
7 |
8 | def flatten_deep(lst: list[T]) -> list:
9 | """
10 | Flattens all depths of a nested array.
11 |
12 | Args:
13 | lst (list[T]): The array to flatten.
14 |
15 | Returns:
16 | list: A new array that has been flattened.
17 |
18 | Example:
19 | >>> flatten_deep([1, [2, [3]], [4, [5, 6]]])
20 | [1, 2, 3, 4, 5, 6]
21 | """
22 | return flatten(lst, float("inf"))
23 |
--------------------------------------------------------------------------------
/src/usepy/list/key_by.py:
--------------------------------------------------------------------------------
1 | from typing import Callable, TypeVar, Dict
2 |
3 | T = TypeVar('T')
4 | K = TypeVar('K')
5 |
6 |
7 | def key_by(lst: list[T], get_key_from_item: Callable[[T], K]) -> Dict[K, T]:
8 | """
9 | Maps each element of a list based on a provided key-generating function.
10 |
11 | This function takes a list and a function that generates a key from each element. It returns
12 | a dictionary where the keys are the generated keys and the values are the corresponding elements.
13 | If there are multiple elements generating the same key, the last element among them is used
14 | as the value.
15 |
16 | Args:
17 | lst (list[T]): The list of elements to be mapped.
18 | get_key_from_item (Callable[[T], K]): A function that generates a key from an element.
19 |
20 | Returns:
21 | Dict[K, T]: A dictionary where keys are mapped to each element of a list.
22 |
23 | Example:
24 | >>> array = [
25 | ... {'category': 'fruit', 'name': 'apple'},
26 | ... {'category': 'fruit', 'name': 'banana'},
27 | ... {'category': 'vegetable', 'name': 'carrot'}
28 | ... ]
29 | >>> key_by(array, lambda item: item['category'])
30 | {'fruit': {'category': 'fruit', 'name': 'banana'}, 'vegetable': {'category': 'vegetable', 'name': 'carrot'}}
31 | """
32 | result: Dict[K, T] = {}
33 |
34 | for item in lst:
35 | key = get_key_from_item(item)
36 | result[key] = item
37 |
38 | return result
39 |
--------------------------------------------------------------------------------
/src/usepy/list/last.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar, Sequence
2 |
3 | T = TypeVar("T")
4 |
5 |
6 | def last(array: Sequence[T]) -> T | None:
7 | """
8 | Gets the last element of `array`.
9 |
10 | :param array: The array to query.
11 | :type array: Sequence[T]
12 | :return: Returns the last element of `array`.
13 | :rtype: T | None
14 |
15 | :Example:
16 |
17 | >>> last([1, 2, 3])
18 | 3
19 | """
20 | return array[-1] if array else None
21 |
--------------------------------------------------------------------------------
/src/usepy/list/sample.py:
--------------------------------------------------------------------------------
1 | import random
2 | from typing import TypeVar, Sequence
3 |
4 | T = TypeVar("T")
5 |
6 |
7 | def sample(arr: Sequence[T], count: int = 1) -> T | list[T]:
8 | """
9 | Returns a random element from a sequence.
10 |
11 | This function takes a sequence (e.g., list, tuple) and returns a single element
12 | selected randomly from the sequence.
13 |
14 | Args:
15 | arr (Sequence[T]): The sequence to sample from.
16 |
17 | Returns:
18 | T: A random element from the sequence.
19 |
20 | Example:
21 | >>> sample([1, 2, 3, 4, 5])
22 | 3
23 | >>> sample(('apple', 'banana', 'cherry'))
24 | 'banana'
25 | >>> sample([1, 2, 3, 4, 5], 2)
26 | [3, 1]
27 | """
28 | if count == 1:
29 | return random.choice(arr)
30 | return random.sample(arr, count)
31 |
--------------------------------------------------------------------------------
/src/usepy/list/shuffle.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar, Sequence
2 | import random
3 |
4 | T = TypeVar('T')
5 |
6 |
7 | def shuffle(arr: Sequence[T]) -> list[T]:
8 | """
9 | Randomizes the order of elements in a sequence using the Fisher-Yates algorithm.
10 |
11 | This function takes a sequence and returns a new list with its elements shuffled in a random order.
12 |
13 | Args:
14 | arr (Sequence[T]): The sequence to shuffle.
15 |
16 | Returns:
17 | list[T]: A new list with its elements shuffled in random order.
18 |
19 | Examples:
20 | >>> arr = [1, 2, 3, 4, 5]
21 | >>> shuffled_arr = shuffle(arr)
22 | >>> sorted(shuffled_arr) == sorted(arr)
23 | True
24 | >>> len(shuffled_arr) == len(arr)
25 | True
26 | """
27 | result = list(arr)
28 |
29 | for i in range(len(result) - 1, 0, -1):
30 | j = random.randint(0, i)
31 | result[i], result[j] = result[j], result[i]
32 |
33 | return result
34 |
--------------------------------------------------------------------------------
/src/usepy/list/some.py:
--------------------------------------------------------------------------------
1 | from typing import Callable, List, TypeVar
2 |
3 | T = TypeVar('T')
4 |
5 |
6 | def some(array: List[T], fn: Callable[[T], bool]) -> bool:
7 | """
8 | Checks if at least one element in the given array satisfies the provided condition function.
9 |
10 | Args:
11 | array (List[T]): The input array to be checked.
12 | fn (Callable[[T], bool]): The condition function that takes an element and returns a boolean value.
13 | If the function returns True for any element, the function will return True.
14 |
15 | Returns:
16 | bool: True if at least one element satisfies the condition function, False otherwise.
17 |
18 | Example:
19 | >>> nums = [1, 2, 3, 4, 5]
20 | >>> is_even = lambda x: x % 2 == 0
21 | >>> some(nums, is_even)
22 | True
23 | >>> is_negative = lambda x: x < 0
24 | >>> some(nums, is_negative)
25 | False
26 | """
27 | from functools import reduce
28 | from operator import or_
29 |
30 | return reduce(or_, [fn(element) for element in array])
31 |
--------------------------------------------------------------------------------
/src/usepy/list/union.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar, Iterable
2 | from usepy.list.uniq import uniq
3 |
4 | T = TypeVar('T')
5 |
6 |
7 | def union(*lsts: Iterable[T]) -> list[T]:
8 | """
9 | Creates a list of unique values from all given iterables.
10 |
11 | This function takes any number of iterables, merges them into a single iterable, and returns a new list
12 | containing only the unique values from the merged iterable.
13 |
14 | Args:
15 | *lsts (Iterable[T]): The iterables to merge and filter for unique values.
16 |
17 | Returns:
18 | list[T]: A new list of unique values.
19 |
20 | Example:
21 | >>> array1 = [1, 2, 3]
22 | >>> array2 = [3, 4, 5]
23 | >>> array3 = [5, 6, 7]
24 | >>> union(array1,array2,array3)
25 | [1, 2, 3, 4, 5, 6, 7]
26 | """
27 | merged = [item for arr in lsts for item in arr]
28 | return uniq(merged)
29 |
--------------------------------------------------------------------------------
/src/usepy/list/uniq.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar, Sequence
2 |
3 | T = TypeVar('T')
4 |
5 |
6 | def uniq(lst: Sequence[T]) -> list[T]:
7 | """
8 | Returns a new list containing only the unique elements from the input sequence.
9 |
10 | Args:
11 | lst (Sequence[T]): The input sequence.
12 |
13 | Returns:
14 | list[T]: A new list containing only the unique elements from the input sequence.
15 |
16 | Example:
17 | >>> uniq([1, 2, 3, 2, 1])
18 | [1, 2, 3]
19 | """
20 | return list(set(lst))
21 |
--------------------------------------------------------------------------------
/src/usepy/list/without.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar, Sequence, Set
2 |
3 | T = TypeVar('T')
4 |
5 |
6 | def without(array: Sequence[T], *values: T) -> list[T]:
7 | """
8 | Creates a new list that excludes all specified values from the given array.
9 |
10 | It correctly excludes `NaN`, as it compares values using the `==` operator
11 | in Python, which handles `NaN` correctly.
12 |
13 | Args:
14 | array (Sequence[T]): The sequence to filter.
15 | *values (T): The values to exclude.
16 |
17 | Returns:
18 | list[T]: A new list without the specified values.
19 |
20 | Examples:
21 | >>> without([1, 2, 3, 4, 5], 2, 4)
22 | [1, 3, 5]
23 |
24 | >>> without(['a', 'b', 'c', 'a'], 'a')
25 | ['b', 'c']
26 | """
27 | values_set: Set[T] = set(values)
28 | return [item for item in array if item not in values_set]
29 |
--------------------------------------------------------------------------------
/src/usepy/list/zip_dict.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar, List, Dict
2 |
3 | P = TypeVar('P')
4 | V = TypeVar('V')
5 |
6 |
7 | def zip_dict(keys: List[P], values: List[V]) -> Dict[P, V]:
8 | """
9 | Combines two arrays, one of property names and one of corresponding values, into a single object.
10 |
11 | This function takes two arrays: one containing property names and another containing corresponding values.
12 | It returns a new dictionary where the property names from the first array are keys, and the corresponding elements
13 | from the second array are values. If the `keys` array is longer than the `values` array, the remaining keys will
14 | have `None` as their values.
15 |
16 | Args:
17 | keys (List[P]): An array of property names.
18 | values (List[V]): An array of values corresponding to the property names.
19 |
20 | Returns:
21 | Dict[P, V]: A new dictionary composed of the given property names and values.
22 |
23 | Examples:
24 | >>> key1 = ['a', 'b', 'c']
25 | >>> key2 = [1, 2, 3]
26 | >>> zip_dict(key1, key2)
27 | {'a': 1, 'b': 2, 'c': 3}
28 |
29 | >>> key1 = ['a', 'b', 'c']
30 | >>> key2 = [1, 2]
31 | >>> zip_dict(key1, key2)
32 | {'a': 1, 'b': 2, 'c': None}
33 |
34 | >>> key1 = ['a', 'b']
35 | >>> key2 = [1, 2, 3]
36 | >>> zip_dict(key1, key2)
37 | {'a': 1, 'b': 2}
38 | """
39 | result = {}
40 | for i, key in enumerate(keys):
41 | result[key] = values[i] if i < len(values) else None
42 | return result
43 |
--------------------------------------------------------------------------------
/src/usepy/list/zip_tuple.py:
--------------------------------------------------------------------------------
1 | from typing import Tuple, List, Any
2 |
3 |
4 | def zip_tuple(*lsts: List[Any]) -> List[Tuple[Any, ...]]:
5 | """
6 | Combines multiple arrays into a single array of tuples.
7 |
8 | This function takes multiple arrays and returns a new array where each element is a tuple
9 | containing the corresponding elements from the input arrays. If the input arrays are of
10 | different lengths, the resulting array will have the length of the longest input array,
11 | with `None` values for missing elements.
12 |
13 | Args:
14 | *lsts: The arrays to zip together.
15 |
16 | Returns:
17 | A new array of tuples containing the corresponding elements from the input arrays.
18 |
19 | Examples:
20 | >>> arr1 = [1, 2, 3]
21 | >>> arr2 = ['a', 'b', 'c']
22 | >>> zip_tuple(arr1, arr2)
23 | [(1, 'a'), (2, 'b'), (3, 'c')]
24 |
25 | >>> arr3 = [True, False]
26 | >>> zip_tuple(arr1, arr2, arr3)
27 | [(1, 'a', True), (2, 'b', False), (3, 'c', None)]
28 | """
29 | result: List[Tuple[Any, ...]] = []
30 |
31 | max_index = max(len(arr) for arr in lsts)
32 |
33 | for i in range(max_index):
34 | element: List[Any] = []
35 |
36 | for arr in lsts:
37 | element.append(arr[i] if i < len(arr) else None)
38 |
39 | result.append(tuple(element))
40 |
41 | return result
42 |
--------------------------------------------------------------------------------
/src/usepy/misc/__init__.py:
--------------------------------------------------------------------------------
1 | from .get_function_name import get_function_name
2 | from .dynamic_import import dynamic_import
3 |
4 | __all__ = ["get_function_name", "dynamic_import"]
5 |
--------------------------------------------------------------------------------
/src/usepy/misc/dynamic_import.py:
--------------------------------------------------------------------------------
1 | import importlib
2 | import os
3 | import sys
4 |
5 |
6 | def dynamic_import(module_string: str):
7 | """
8 | import function from string
9 | >>> module, function = dynamic_import("json.dumps")
10 | >>> function.__name__
11 | 'dumps'
12 | >>> module.__name__
13 | 'json'
14 | """
15 | try:
16 | module_name, function_name = module_string.rsplit(".", 1)
17 | module = importlib.import_module(module_name)
18 | except ImportError:
19 | module_path, module_name, function_name = module_string.rsplit(".", 2)
20 | sys.path.append(os.path.abspath(module_path))
21 | module = importlib.import_module(module_name)
22 | except ValueError:
23 | function_name = ""
24 | module = importlib.import_module(module_string)
25 | except Exception as e:
26 | raise ImportError(f"无法导入 {module_string}")
27 |
28 | function = getattr(module, function_name, None)
29 | return module, function
30 |
--------------------------------------------------------------------------------
/src/usepy/misc/get_function_name.py:
--------------------------------------------------------------------------------
1 | import inspect
2 |
3 |
4 | def get_function_name():
5 | """
6 | get the name of the current function
7 |
8 | >>> def a():
9 | ... def b():
10 | ... return get_function_name()
11 | ... return b
12 | ...
13 | >>> a()()
14 | 'b'
15 | """
16 | return inspect.currentframe().f_back.f_code.co_name
17 |
--------------------------------------------------------------------------------
/src/usepy/string/__init__.py:
--------------------------------------------------------------------------------
1 | from .camel_case import camel_case
2 | from .capitalize import capitalize
3 | from .kebab_case import kebab_case
4 | from .left import left
5 | from .lower_case import lower_case
6 | from .middle import middle
7 | from .middle_batch import middle_batch
8 | from .pascal_case import pascal_case
9 | from .right import right
10 | from .snake_case import snake_case
11 |
12 | __all__ = [
13 | "camel_case",
14 | "capitalize",
15 | "kebab_case",
16 | "left",
17 | "lower_case",
18 | "middle",
19 | "middle_batch",
20 | "pascal_case",
21 | "right",
22 | "snake_case",
23 | ]
24 |
--------------------------------------------------------------------------------
/src/usepy/string/_get_section.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, Tuple
2 |
3 |
4 | def get_section(
5 | original_str: str, start_str: Optional[str] = None, end_str: Optional[str] = None
6 | ) -> Tuple[Optional[str], Optional[int], Optional[int]]:
7 | """
8 | Get the substring between two given substrings in a string.
9 |
10 | Args:
11 | original_str (str): The original string to search in.
12 | start_str (Optional[str], optional): The substring to start the search from.
13 | If not provided, the search starts from the beginning of the string.
14 | end_str (Optional[str], optional): The substring to end the search at.
15 | If not provided, the search ends at the end of the string.
16 |
17 | Returns:
18 | Tuple[Optional[str], Optional[int], Optional[int]]:
19 | - The substring between the start and end substrings, or None if not found.
20 | - The starting index of the substring, or None if not found.
21 | - The ending index of the substring, or None if not found.
22 |
23 | Examples:
24 | >>> get_section('abc123def', 'abc', 'def')
25 | ('123', 3, 6)
26 | >>> get_section('abc123def', 'abc')
27 | ('123def', 3, 9)
28 | >>> get_section('abc123def', end_str='def')[0]
29 | 'abc123'
30 | """
31 | if start_str is None:
32 | start_index = 0
33 | else:
34 | start_index = original_str.find(start_str)
35 | if start_index >= 0:
36 | start_index += len(start_str)
37 | else:
38 | return None, start_index, None
39 |
40 | if end_str is None:
41 | end_index = len(original_str)
42 | else:
43 | end_index = original_str.find(end_str, start_index)
44 |
45 | if end_index >= 0:
46 | return original_str[start_index:end_index], start_index, end_index
47 |
48 | return None, None, None
49 |
--------------------------------------------------------------------------------
/src/usepy/string/_get_words.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar
2 | import re
3 |
4 | T = TypeVar('T', bound=str)
5 |
6 | CASE_SPLIT_PATTERN = re.compile(r'[A-Z]?[a-z]+|[0-9]+|[A-Z]+(?![a-z])', re.VERBOSE)
7 |
8 |
9 | def get_words(string: T) -> list[T]:
10 | """
11 | Splits a string into words based on whitespace and non-alphanumeric characters.
12 |
13 | Args:
14 | string (str): The input string to be split into words.
15 |
16 | Returns:
17 | list[str]: A list of words extracted from the input string.
18 |
19 | Example:
20 | >>> get_words('hello world')
21 | ['hello', 'world']
22 | >>> get_words('hello-world/foo_bar')
23 | ['hello', 'world', 'foo', 'bar']
24 | """
25 | return re.findall(CASE_SPLIT_PATTERN, string)
26 |
--------------------------------------------------------------------------------
/src/usepy/string/camel_case.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar
2 |
3 | from usepy.string.capitalize import capitalize
4 |
5 | T = TypeVar('T', bound=str)
6 |
7 |
8 | def camel_case(string: T) -> T:
9 | """
10 | Converts a string to camel case.
11 |
12 | Camel case is the naming convention in which each word is written in lowercase
13 | and separated by an underscore (_) character.
14 |
15 | Args:
16 | string (T): The input string to be converted to camel case.
17 |
18 | Returns:
19 | str: The converted string in camel case.
20 |
21 | Examples:
22 | >>> camel_case('camelCase')
23 | 'camelCase'
24 | >>> camel_case('some whitespace')
25 | 'someWhitespace'
26 | >>> camel_case('hyphen-text')
27 | 'hyphenText'
28 | >>> camel_case('HTTPRequest')
29 | 'httpRequest'
30 | """
31 | from usepy.string._get_words import get_words
32 |
33 | words = get_words(string)
34 |
35 | if not words:
36 | return ""
37 |
38 | first, *rest = words
39 | capitalized_rest = [capitalize(word) for word in rest]
40 |
41 | return f"{first.lower()}{''.join(capitalized_rest)}"
42 |
--------------------------------------------------------------------------------
/src/usepy/string/capitalize.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar
2 |
3 | T = TypeVar('T', bound=str)
4 |
5 |
6 | def capitalize(string: T) -> T:
7 | """
8 | Converts the first character of a string to uppercase and the remaining characters to lowercase.
9 |
10 | Args:
11 | string (T): The string to be capitalized.
12 |
13 | Returns:
14 | Capitalize[T]: The capitalized string.
15 |
16 | Examples:
17 | >>> capitalize('fred')
18 | 'Fred'
19 | >>> capitalize('FRED')
20 | 'Fred'
21 | """
22 | return string.title()
23 |
--------------------------------------------------------------------------------
/src/usepy/string/kebab_case.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 |
4 | def kebab_case(string: Union[str, bytes]) -> str:
5 | """
6 | Converts a string to kebab case.
7 |
8 | Kebab case is the naming convention in which each word is written in lowercase
9 | and separated by a dash (-) character.
10 |
11 | Args:
12 | string (Union[str, bytes]): The input string to be converted to kebab case.
13 |
14 | Returns:
15 | str: The converted string in kebab case.
16 |
17 | Examples:
18 | >>> kebab_case('camelCase')
19 | 'camel-case'
20 | >>> kebab_case('some whitespace')
21 | 'some-whitespace'
22 | >>> kebab_case('hyphen-text')
23 | 'hyphen-text'
24 | >>> kebab_case('HTTPRequest')
25 | 'http-request'
26 | """
27 | from usepy.string._get_words import get_words
28 |
29 | if isinstance(string, bytes):
30 | string = string.decode('utf-8')
31 |
32 | words = get_words(string)
33 | return '-'.join(word.lower() for word in words)
34 |
--------------------------------------------------------------------------------
/src/usepy/string/left.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 |
4 |
5 |
6 | def left(original_str: str, end_str: str) -> Optional[str]:
7 | """
8 | Get the substring to the left of a given substring in a string.
9 |
10 | Args:
11 | original_str (str): The original string to search in.
12 | end_str (str): The substring to end the search at.
13 |
14 | Returns:
15 | Optional[str]: The substring to the left of the given end substring, or None if not found.
16 |
17 | Examples:
18 | >>> left('abc123def', 'def')
19 | 'abc123'
20 | """
21 | from usepy.string._get_section import get_section
22 |
23 | result, *_ = get_section(original_str, end_str=end_str)
24 | return result
25 |
--------------------------------------------------------------------------------
/src/usepy/string/lower_case.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 |
4 | def lower_case(string: Union[str, bytes]) -> str:
5 | """
6 | Converts a string to lower case.
7 |
8 | Lower case is the naming convention in which each word is written in lowercase
9 | and separated by a space ( ) character.
10 |
11 | Args:
12 | string (Union[str, bytes]): The input string to be converted to lower case.
13 |
14 | Returns:
15 | str: The converted string in lower case.
16 |
17 | Examples:
18 | >>> lower_case('camelCase')
19 | 'camel case'
20 | >>> lower_case('some whitespace')
21 | 'some whitespace'
22 | >>> lower_case('hyphen-text')
23 | 'hyphen text'
24 | >>> lower_case('HTTPRequest')
25 | 'http request'
26 | """
27 | from usepy.string._get_words import get_words
28 |
29 | if isinstance(string, bytes):
30 | string = string.decode('utf-8')
31 |
32 | words = get_words(string)
33 | return ' '.join(word.lower() for word in words)
34 |
--------------------------------------------------------------------------------
/src/usepy/string/middle.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 |
4 | def middle(
5 | original_str: str, start_str: Optional[str] = None, end_str: Optional[str] = None
6 | ) -> Optional[str]:
7 | """
8 | Get the substring between two given substrings in a string.
9 |
10 | Args:
11 | original_str (str): The original string to search in.
12 | start_str (Optional[str], optional): The substring to start the search from.
13 | If not provided, the search starts from the beginning of the string.
14 | end_str (Optional[str], optional): The substring to end the search at.
15 | If not provided, the search ends at the end of the string.
16 |
17 | Returns:
18 | Optional[str]: The substring between the start and end substrings, or None if not found.
19 |
20 | Examples:
21 | >>> middle('abc123def', 'abc', 'def')
22 | '123'
23 | """
24 | from usepy.string._get_section import get_section
25 |
26 | result, _, _ = get_section(original_str, start_str, end_str)
27 | return result
28 |
--------------------------------------------------------------------------------
/src/usepy/string/middle_batch.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, List
2 |
3 |
4 | def middle_batch(
5 | original_str: str,
6 | start_str: Optional[str] = None,
7 | end_str: Optional[str] = None,
8 | max_count: Optional[int] = None,
9 | ) -> List[str]:
10 | """
11 | Get a list of substrings between two given substrings in a string.
12 |
13 | Args:
14 | original_str (str): The original string to search in.
15 | start_str (Optional[str], optional): The substring to start the search from.
16 | If not provided, the search starts from the beginning of the string.
17 | end_str (Optional[str], optional): The substring to end the search at.
18 | If not provided, the search ends at the end of the string.
19 | max_count (Optional[int], optional): The maximum number of substrings to return.
20 | If not provided or set to None, all substrings will be returned.
21 |
22 | Returns:
23 | List[str]: A list of substrings between the start and end substrings.
24 |
25 | Examples:
26 | >>> middle_batch('abc123def456abc789def', 'abc', 'def')
27 | ['123', '789']
28 | >>> middle_batch('abc123def456abc789def', 'abc', 'def', 1)
29 | ['123']
30 | """
31 | from usepy.string._get_section import get_section
32 |
33 | result = []
34 | original_str_copy = original_str
35 |
36 | while True:
37 | substring, start_index, end_index = get_section(original_str_copy, start_str, end_str)
38 | if substring is None:
39 | break
40 |
41 | result.append(substring)
42 | original_str_copy = original_str_copy[end_index + len(end_str or ""):]
43 |
44 | if max_count is not None:
45 | return result[:max_count]
46 | else:
47 | return result
48 |
--------------------------------------------------------------------------------
/src/usepy/string/pascal_case.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | from usepy.string.capitalize import capitalize
4 |
5 |
6 | def pascal_case(string: Union[str, bytes]) -> str:
7 | """
8 | Converts a string to Pascal case.
9 |
10 | Pascal case is the naming convention in which each word is capitalized and concatenated without any separator characters.
11 |
12 | Args:
13 | string (Union[str, bytes]): The input string to be converted to Pascal case.
14 |
15 | Returns:
16 | str: The converted string in Pascal case.
17 |
18 | Examples:
19 | >>> pascal_case('pascalCase')
20 | 'PascalCase'
21 | >>> pascal_case('some whitespace')
22 | 'SomeWhitespace'
23 | >>> pascal_case('hyphen-text')
24 | 'HyphenText'
25 | >>> pascal_case('HTTPRequest')
26 | 'HttpRequest'
27 | """
28 | from usepy.string._get_words import get_words
29 |
30 | if isinstance(string, bytes):
31 | string = string.decode('utf-8')
32 |
33 | words = get_words(string)
34 | return ''.join(capitalize(word) for word in words)
--------------------------------------------------------------------------------
/src/usepy/string/right.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 |
4 | def right(original_str: str, start_str: str) -> Optional[str]:
5 | """
6 | Get the substring to the right of a given substring in a string.
7 |
8 | Args:
9 | original_str (str): The original string to search in.
10 | start_str (str): The substring to start the search from.
11 |
12 | Returns:
13 | Optional[str]: The substring to the right of the given start substring, or None if not found.
14 |
15 | Examples:
16 | >>> right('abc123def', 'abc')
17 | '123def'
18 | """
19 | from usepy.string._get_section import get_section
20 |
21 | result, *_ = get_section(original_str, start_str=start_str)
22 | return result
23 |
--------------------------------------------------------------------------------
/src/usepy/string/snake_case.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 |
4 | def snake_case(string: Union[str, bytes]) -> str:
5 | """
6 | Converts a string to snake case.
7 |
8 | Snake case is the naming convention in which each word is written in lowercase
9 | and separated by an underscore (_) character.
10 |
11 | Args:
12 | string (Union[str, bytes]): The input string to be converted to snake case.
13 |
14 | Returns:
15 | str: The converted string in snake case.
16 |
17 | Examples:
18 | >>> snake_case('camelCase')
19 | 'camel_case'
20 | >>> snake_case('some whitespace')
21 | 'some_whitespace'
22 | >>> snake_case('hyphen-text')
23 | 'hyphen_text'
24 | >>> snake_case('HTTPRequest')
25 | 'http_request'
26 | """
27 | from usepy.string._get_words import get_words
28 |
29 | if isinstance(string, bytes):
30 | string = string.decode("utf-8")
31 |
32 | words = get_words(string)
33 | return "_".join(word.lower() for word in words)
34 |
--------------------------------------------------------------------------------
/src/usepy/validator/__init__.py:
--------------------------------------------------------------------------------
1 | from .is_async_function import is_async_function
2 | from .is_url import is_url
3 |
4 | __all__ = ["is_async_function", "is_url"]
5 |
--------------------------------------------------------------------------------
/src/usepy/validator/is_async_function.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from typing import Callable
3 |
4 |
5 | def is_async_function(func: Callable) -> bool:
6 | """
7 | check if the function is an async function
8 | >>> is_async_function(lambda x: x)
9 | False
10 | """
11 |
12 | return asyncio.iscoroutinefunction(func)
13 |
--------------------------------------------------------------------------------
/src/usepy/validator/is_url.py:
--------------------------------------------------------------------------------
1 | def is_url(url: str) -> bool:
2 | """
3 | check if the given string is a valid URL.
4 |
5 | Args:
6 | url (str): the string to check.
7 |
8 | Returns:
9 | bool: if the string is a valid URL, return True, otherwise return False.
10 |
11 | Examples:
12 | >>> is_url("https://www.google.com")
13 | True
14 | """
15 | if not url or not isinstance(url, str):
16 | return False
17 |
18 | from urllib.parse import urlparse
19 |
20 | try:
21 | result = urlparse(url)
22 | if all([result.scheme, result.netloc]) and result.scheme in [
23 | "http",
24 | "https",
25 | ]:
26 | return True
27 | except ValueError:
28 | return False
29 |
30 | return False
31 |
--------------------------------------------------------------------------------
/tests/test_addict.py:
--------------------------------------------------------------------------------
1 | import copy
2 | import json
3 | import pickle
4 | import unittest
5 |
6 | from usepy import AdDict as Dict
7 |
8 |
9 | # test whether unittests pass on child classes
10 | class CHILD_CLASS(Dict):
11 | child_class_attribute = "child class attribute"
12 |
13 | def child_instance_attribute(self):
14 | return "child instance attribute"
15 |
16 |
17 | TEST_VAL = [1, 2, 3]
18 | TEST_DICT = {"a": {"b": {"c": TEST_VAL}}}
19 |
20 |
21 | class AbstractTestsClass(object):
22 | dict_class = None
23 |
24 | def test_set_one_level_item(self):
25 | some_dict = {"a": TEST_VAL}
26 | prop = self.dict_class()
27 | prop["a"] = TEST_VAL
28 | self.assertDictEqual(prop, some_dict)
29 |
30 | def test_set_two_level_items(self):
31 | some_dict = {"a": {"b": TEST_VAL}}
32 | prop = self.dict_class()
33 | prop["a"]["b"] = TEST_VAL
34 | self.assertDictEqual(prop, some_dict)
35 |
36 | def test_set_three_level_items(self):
37 | prop = self.dict_class()
38 | prop["a"]["b"]["c"] = TEST_VAL
39 | self.assertDictEqual(prop, TEST_DICT)
40 |
41 | def test_set_one_level_property(self):
42 | prop = self.dict_class()
43 | prop.a = TEST_VAL
44 | self.assertDictEqual(prop, {"a": TEST_VAL})
45 |
46 | def test_set_two_level_properties(self):
47 | prop = self.dict_class()
48 | prop.a.b = TEST_VAL
49 | self.assertDictEqual(prop, {"a": {"b": TEST_VAL}})
50 |
51 | def test_set_three_level_properties(self):
52 | prop = self.dict_class()
53 | prop.a.b.c = TEST_VAL
54 | self.assertDictEqual(prop, TEST_DICT)
55 |
56 | def test_init_with_dict(self):
57 | self.assertDictEqual(TEST_DICT, Dict(TEST_DICT))
58 |
59 | def test_init_with_kws(self):
60 | prop = self.dict_class(a=2, b={"a": 2}, c=[{"a": 2}])
61 | self.assertDictEqual(prop, {"a": 2, "b": {"a": 2}, "c": [{"a": 2}]})
62 |
63 | def test_init_with_tuples(self):
64 | prop = self.dict_class((0, 1), (1, 2), (2, 3))
65 | self.assertDictEqual(prop, {0: 1, 1: 2, 2: 3})
66 |
67 | def test_init_with_list(self):
68 | prop = self.dict_class([(0, 1), (1, 2), (2, 3)])
69 | self.assertDictEqual(prop, {0: 1, 1: 2, 2: 3})
70 |
71 | def test_init_with_generator(self):
72 | prop = self.dict_class(((i, i + 1) for i in range(3)))
73 | self.assertDictEqual(prop, {0: 1, 1: 2, 2: 3})
74 |
75 | def test_init_with_tuples_and_empty_list(self):
76 | prop = self.dict_class((0, 1), [], (2, 3))
77 | self.assertDictEqual(prop, {0: 1, 2: 3})
78 |
79 | def test_init_raises(self):
80 | def init():
81 | self.dict_class(5)
82 |
83 | def init2():
84 | Dict("a")
85 |
86 | self.assertRaises(TypeError, init)
87 | self.assertRaises(ValueError, init2)
88 |
89 | def test_init_with_empty_stuff(self):
90 | a = self.dict_class({})
91 | b = self.dict_class([])
92 | self.assertDictEqual(a, {})
93 | self.assertDictEqual(b, {})
94 |
95 | def test_init_with_list_of_dicts(self):
96 | a = self.dict_class({"a": [{"b": 2}]})
97 | self.assertIsInstance(a.a[0], self.dict_class)
98 | self.assertEqual(a.a[0].b, 2)
99 |
100 | def test_init_with_kwargs(self):
101 | a = self.dict_class(a="b", c=dict(d="e", f=dict(g="h")))
102 |
103 | self.assertEqual(a.a, "b")
104 | self.assertIsInstance(a.c, self.dict_class)
105 |
106 | self.assertEqual(a.c.f.g, "h")
107 | self.assertIsInstance(a.c.f, self.dict_class)
108 |
109 | def test_getitem(self):
110 | prop = self.dict_class(TEST_DICT)
111 | self.assertEqual(prop["a"]["b"]["c"], TEST_VAL)
112 |
113 | def test_empty_getitem(self):
114 | prop = self.dict_class()
115 | prop.a.b.c
116 | self.assertEqual(prop, {})
117 |
118 | def test_getattr(self):
119 | prop = self.dict_class(TEST_DICT)
120 | self.assertEqual(prop.a.b.c, TEST_VAL)
121 |
122 | def test_isinstance(self):
123 | self.assertTrue(isinstance(self.dict_class(), dict))
124 |
125 | def test_str(self):
126 | prop = self.dict_class(TEST_DICT)
127 | self.assertEqual(str(prop), str(TEST_DICT))
128 |
129 | def test_json(self):
130 | some_dict = TEST_DICT
131 | some_json = json.dumps(some_dict)
132 | prop = self.dict_class()
133 | prop.a.b.c = TEST_VAL
134 | prop_json = json.dumps(prop)
135 | self.assertEqual(some_json, prop_json)
136 |
137 | def test_delitem(self):
138 | prop = self.dict_class({"a": 2})
139 | del prop["a"]
140 | self.assertDictEqual(prop, {})
141 |
142 | def test_delitem_nested(self):
143 | prop = self.dict_class(TEST_DICT)
144 | del prop["a"]["b"]["c"]
145 | self.assertDictEqual(prop, {"a": {"b": {}}})
146 |
147 | def test_delattr(self):
148 | prop = self.dict_class({"a": 2})
149 | del prop.a
150 | self.assertDictEqual(prop, {})
151 |
152 | def test_delattr_nested(self):
153 | prop = self.dict_class(TEST_DICT)
154 | del prop.a.b.c
155 | self.assertDictEqual(prop, {"a": {"b": {}}})
156 |
157 | def test_delitem_delattr(self):
158 | prop = self.dict_class(TEST_DICT)
159 | del prop.a["b"]
160 | self.assertDictEqual(prop, {"a": {}})
161 |
162 | def test_tuple_key(self):
163 | prop = self.dict_class()
164 | prop[(1, 2)] = 2
165 | self.assertDictEqual(prop, {(1, 2): 2})
166 | self.assertEqual(prop[(1, 2)], 2)
167 |
168 | def test_set_prop_invalid(self):
169 | prop = self.dict_class()
170 |
171 | def set_keys():
172 | prop.keys = 2
173 |
174 | def set_items():
175 | prop.items = 3
176 |
177 | self.assertRaises(AttributeError, set_keys)
178 | self.assertRaises(AttributeError, set_items)
179 | self.assertDictEqual(prop, {})
180 |
181 | def test_dir(self):
182 | key = "a"
183 | prop = self.dict_class({key: 1})
184 | dir_prop = dir(prop)
185 |
186 | dir_dict = dir(self.dict_class)
187 | for d in dir_dict:
188 | self.assertTrue(d in dir_prop, d)
189 |
190 | self.assertTrue("__methods__" not in dir_prop)
191 | self.assertTrue("__members__" not in dir_prop)
192 |
193 | def test_dir_with_members(self):
194 | prop = self.dict_class({"__members__": 1})
195 | dir(prop)
196 | self.assertTrue("__members__" in prop.keys())
197 |
198 | def test_to_dict(self):
199 | nested = {"a": [{"a": 0}, 2], "b": {}, "c": 2}
200 | prop = self.dict_class(nested)
201 | regular = prop.to_dict()
202 | self.assertDictEqual(regular, prop)
203 | self.assertDictEqual(regular, nested)
204 | self.assertNotIsInstance(regular, self.dict_class)
205 |
206 | def get_attr():
207 | regular.a = 2
208 |
209 | self.assertRaises(AttributeError, get_attr)
210 |
211 | def get_attr_deep():
212 | regular["a"][0].a = 1
213 |
214 | self.assertRaises(AttributeError, get_attr_deep)
215 |
216 | def test_to_dict_with_tuple(self):
217 | nested = {"a": ({"a": 0}, {2: 0})}
218 | prop = self.dict_class(nested)
219 | regular = prop.to_dict()
220 | self.assertDictEqual(regular, prop)
221 | self.assertDictEqual(regular, nested)
222 | self.assertIsInstance(regular["a"], tuple)
223 | self.assertNotIsInstance(regular["a"][0], self.dict_class)
224 |
225 | def test_update(self):
226 | old = self.dict_class()
227 | old.child.a = "a"
228 | old.child.b = "b"
229 | old.foo = "c"
230 |
231 | new = self.dict_class()
232 | new.child.b = "b2"
233 | new.child.c = "c"
234 | new.foo.bar = True
235 |
236 | old.update(new)
237 |
238 | reference = {"foo": {"bar": True}, "child": {"a": "a", "c": "c", "b": "b2"}}
239 |
240 | self.assertDictEqual(old, reference)
241 |
242 | def test_update_with_lists(self):
243 | org = self.dict_class()
244 | org.a = [1, 2, {"a": "superman"}]
245 | someother = self.dict_class()
246 | someother.b = [{"b": 123}]
247 | org.update(someother)
248 |
249 | correct = {"a": [1, 2, {"a": "superman"}], "b": [{"b": 123}]}
250 |
251 | org.update(someother)
252 | self.assertDictEqual(org, correct)
253 | self.assertIsInstance(org.b[0], dict)
254 |
255 | def test_update_with_kws(self):
256 | org = self.dict_class(one=1, two=2)
257 | someother = self.dict_class(one=3)
258 | someother.update(one=1, two=2)
259 | self.assertDictEqual(org, someother)
260 |
261 | def test_update_with_args_and_kwargs(self):
262 | expected = {"a": 1, "b": 2}
263 | org = self.dict_class()
264 | org.update({"a": 3, "b": 2}, a=1)
265 | self.assertDictEqual(org, expected)
266 |
267 | def test_update_with_multiple_args(self):
268 | def update():
269 | org.update({"a": 2}, {"a": 1})
270 |
271 | org = self.dict_class()
272 | self.assertRaises(TypeError, update)
273 |
274 | def test_ior_operator(self):
275 | old = self.dict_class()
276 | old.child.a = "a"
277 | old.child.b = "b"
278 | old.foo = "c"
279 |
280 | new = self.dict_class()
281 | new.child.b = "b2"
282 | new.child.c = "c"
283 | new.foo.bar = True
284 |
285 | old |= new
286 |
287 | reference = {"foo": {"bar": True}, "child": {"a": "a", "c": "c", "b": "b2"}}
288 |
289 | self.assertDictEqual(old, reference)
290 |
291 | def test_ior_operator_with_lists(self):
292 | org = self.dict_class()
293 | org.a = [1, 2, {"a": "superman"}]
294 | someother = self.dict_class()
295 | someother.b = [{"b": 123}]
296 | org |= someother
297 |
298 | correct = {"a": [1, 2, {"a": "superman"}], "b": [{"b": 123}]}
299 |
300 | org |= someother
301 | self.assertDictEqual(org, correct)
302 | self.assertIsInstance(org.b[0], dict)
303 |
304 | def test_ior_operator_with_dict(self):
305 | org = self.dict_class(one=1, two=2)
306 | someother = self.dict_class(one=3)
307 | someother |= dict(one=1, two=2)
308 | self.assertDictEqual(org, someother)
309 |
310 | def test_or_operator(self):
311 | old = self.dict_class()
312 | old.child.a = "a"
313 | old.child.b = "b"
314 | old.foo = "c"
315 |
316 | new = self.dict_class()
317 | new.child.b = "b2"
318 | new.child.c = "c"
319 | new.foo.bar = True
320 |
321 | old = old | new
322 |
323 | reference = {"foo": {"bar": True}, "child": {"a": "a", "c": "c", "b": "b2"}}
324 |
325 | self.assertDictEqual(old, reference)
326 |
327 | def test_or_operator_with_lists(self):
328 | org = self.dict_class()
329 | org.a = [1, 2, {"a": "superman"}]
330 | someother = self.dict_class()
331 | someother.b = [{"b": 123}]
332 | org = org | someother
333 |
334 | correct = {"a": [1, 2, {"a": "superman"}], "b": [{"b": 123}]}
335 |
336 | org = org | someother
337 | self.assertDictEqual(org, correct)
338 | self.assertIsInstance(org.b[0], dict)
339 |
340 | def test_ror_operator(self):
341 | org = dict()
342 | org["a"] = [1, 2, {"a": "superman"}]
343 | someother = self.dict_class()
344 | someother.b = [{"b": 123}]
345 | org = org | someother
346 |
347 | correct = {"a": [1, 2, {"a": "superman"}], "b": [{"b": 123}]}
348 |
349 | org = org | someother
350 | self.assertDictEqual(org, correct)
351 | self.assertIsInstance(org, Dict)
352 | self.assertIsInstance(org.b[0], dict)
353 |
354 | def test_or_operator_type_error(self):
355 | old = self.dict_class()
356 | with self.assertRaises(TypeError):
357 | old | "test"
358 |
359 | def test_ror_operator_type_error(self):
360 | old = self.dict_class()
361 | with self.assertRaises(TypeError):
362 | "test" | old
363 |
364 | def test_hook_in_constructor(self):
365 | a_dict = self.dict_class(TEST_DICT)
366 | self.assertIsInstance(a_dict["a"], self.dict_class)
367 |
368 | def test_copy(self):
369 | class MyMutableObject(object):
370 | def __init__(self):
371 | self.attribute = None
372 |
373 | foo = MyMutableObject()
374 | foo.attribute = True
375 |
376 | a = self.dict_class()
377 | a.child.immutable = 42
378 | a.child.mutable = foo
379 |
380 | b = a.copy()
381 |
382 | # immutable object should not change
383 | b.child.immutable = 21
384 | self.assertEqual(a.child.immutable, 21)
385 |
386 | # mutable object should change
387 | b.child.mutable.attribute = False
388 | self.assertEqual(a.child.mutable.attribute, b.child.mutable.attribute)
389 |
390 | # changing child of b should not affect a
391 | b.child = "new stuff"
392 | self.assertTrue(isinstance(a.child, self.dict_class))
393 |
394 | def test_deepcopy(self):
395 | class MyMutableObject(object):
396 | def __init__(self):
397 | self.attribute = None
398 |
399 | foo = MyMutableObject()
400 | foo.attribute = True
401 |
402 | a = self.dict_class()
403 | a.child.immutable = 42
404 | a.child.mutable = foo
405 |
406 | b = copy.deepcopy(a)
407 |
408 | # immutable object should not change
409 | b.child.immutable = 21
410 | self.assertEqual(a.child.immutable, 42)
411 |
412 | # mutable object should not change
413 | b.child.mutable.attribute = False
414 | self.assertTrue(a.child.mutable.attribute)
415 |
416 | # changing child of b should not affect a
417 | b.child = "new stuff"
418 | self.assertTrue(isinstance(a.child, self.dict_class))
419 |
420 | def test_deepcopy2(self):
421 | class MyMutableObject(object):
422 | def __init__(self):
423 | self.attribute = None
424 |
425 | foo = MyMutableObject()
426 | foo.attribute = True
427 |
428 | a = self.dict_class()
429 | a.child.immutable = 42
430 | a.child.mutable = foo
431 |
432 | b = a.deepcopy()
433 |
434 | # immutable object should not change
435 | b.child.immutable = 21
436 | self.assertEqual(a.child.immutable, 42)
437 |
438 | # mutable object should not change
439 | b.child.mutable.attribute = False
440 | self.assertTrue(a.child.mutable.attribute)
441 |
442 | # changing child of b should not affect a
443 | b.child = "new stuff"
444 | self.assertTrue(isinstance(a.child, self.dict_class))
445 |
446 | def test_pickle(self):
447 | a = self.dict_class(TEST_DICT)
448 | self.assertEqual(a, pickle.loads(pickle.dumps(a)))
449 |
450 | def test_add_on_empty_dict(self):
451 | d = self.dict_class()
452 | d.x.y += 1
453 |
454 | self.assertEqual(d.x.y, 1)
455 |
456 | def test_add_on_non_empty_dict(self):
457 | d = self.dict_class()
458 | d.x.y = "defined"
459 |
460 | with self.assertRaises(TypeError):
461 | d.x += 1
462 |
463 | def test_add_on_non_empty_value(self):
464 | d = self.dict_class()
465 | d.x.y = 1
466 | d.x.y += 1
467 |
468 | self.assertEqual(d.x.y, 2)
469 |
470 | def test_add_on_unsupported_type(self):
471 | d = self.dict_class()
472 | d.x.y = "str"
473 |
474 | with self.assertRaises(TypeError):
475 | d.x.y += 1
476 |
477 | def test_init_from_zip(self):
478 | keys = ["a"]
479 | values = [42]
480 | items = zip(keys, values)
481 | d = self.dict_class(items)
482 | self.assertEqual(d.a, 42)
483 |
484 | def test_setdefault_simple(self):
485 | d = self.dict_class()
486 | d.setdefault("a", 2)
487 | self.assertEqual(d.a, 2)
488 | d.setdefault("a", 3)
489 | self.assertEqual(d.a, 2)
490 | d.setdefault("c", []).append(2)
491 | self.assertEqual(d.c, [2])
492 |
493 | def test_setdefault_nested(self):
494 | d = self.dict_class()
495 | d.one.setdefault("two", [])
496 | self.assertEqual(d.one.two, [])
497 | d.one.setdefault("three", []).append(3)
498 | self.assertEqual(d.one.three, [3])
499 |
500 | def test_parent_key_item(self):
501 | a = self.dict_class()
502 | try:
503 | a["keys"]["x"] = 1
504 | except AttributeError as e:
505 | self.fail(e)
506 | try:
507 | a[1].x = 3
508 | except Exception as e:
509 | self.fail(e)
510 | self.assertEqual(a, {"keys": {"x": 1}, 1: {"x": 3}})
511 |
512 | def test_parent_key_prop(self):
513 | a = self.dict_class()
514 | try:
515 | a.y.x = 1
516 | except AttributeError as e:
517 | self.fail(e)
518 | self.assertEqual(a, {"y": {"x": 1}})
519 |
520 | def test_top_freeze_against_top_key(self):
521 | "Test that d.freeze() produces KeyError on d.missing."
522 | d = self.dict_class()
523 | self.assertEqual(d.missing, {})
524 | d.freeze()
525 | with self.assertRaises(KeyError):
526 | d.missing
527 | d.unfreeze()
528 | self.assertEqual(d.missing, {})
529 |
530 | def test_top_freeze_against_nested_key(self):
531 | "Test that d.freeze() produces KeyError on d.inner.missing."
532 | d = self.dict_class()
533 | d.inner.present = TEST_VAL
534 | self.assertIn("inner", d)
535 | self.assertEqual(d.inner.missing, {})
536 | d.freeze()
537 | with self.assertRaises(KeyError):
538 | d.inner.missing
539 | with self.assertRaises(KeyError):
540 | d.missing
541 | d.unfreeze()
542 | self.assertEqual(d.inner.missing, {})
543 | self.assertEqual(d.missing, {})
544 |
545 | def test_nested_freeze_against_top_level(self):
546 | "Test that d.inner.freeze() leaves top-level `d` unfrozen."
547 | d = self.dict_class()
548 | d.inner.present = TEST_VAL
549 | self.assertEqual(d.inner.present, TEST_VAL)
550 | self.assertEqual(d.inner.missing, {})
551 | self.assertEqual(d.missing, {})
552 | d.inner.freeze()
553 | with self.assertRaises(KeyError):
554 | d.inner.missing # d.inner is frozen
555 | self.assertEqual(d.missing, {}) # but not `d` itself
556 | d.inner.unfreeze()
557 | self.assertEqual(d.inner.missing, {})
558 |
559 | def test_top_freeze_disallows_new_key_addition(self):
560 | "Test that d.freeze() disallows adding new keys in d."
561 | d = self.dict_class({"oldKey": None})
562 | d.freeze()
563 | d.oldKey = TEST_VAL # Can set pre-existing key.
564 | self.assertEqual(d.oldKey, TEST_VAL)
565 | with self.assertRaises(KeyError):
566 | d.newKey = TEST_VAL # But can't add a new key.
567 | self.assertNotIn("newKey", d)
568 | d.unfreeze()
569 | d.newKey = TEST_VAL
570 | self.assertEqual(d.newKey, TEST_VAL)
571 |
572 |
573 | class DictTests(unittest.TestCase, AbstractTestsClass):
574 | dict_class = Dict
575 |
576 |
577 | class ChildDictTests(unittest.TestCase, AbstractTestsClass):
578 | dict_class = CHILD_CLASS
579 |
580 |
581 | """
582 | Allow for these test cases to be run from the command line
583 | via `python test_addict.py`
584 | """
585 | if __name__ == "__main__":
586 | test_classes = (DictTests, ChildDictTests)
587 | loader = unittest.TestLoader()
588 | runner = unittest.TextTestRunner(verbosity=2)
589 | for class_ in test_classes:
590 | loaded_tests = loader.loadTestsFromTestCase(class_)
591 | runner.run(loaded_tests)
592 |
--------------------------------------------------------------------------------
/tests/test_converter.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from usepy.converter import to_list, to_md5, to_string, to_set, to_bool
3 |
4 |
5 | @pytest.mark.parametrize(
6 | "input, expected",
7 | [
8 | ("abc", ["a", "b", "c"]),
9 | ([1, 2, 3], [1, 2, 3]),
10 | ({"a": 1, "b": 2}, ["a", "b"]),
11 | ],
12 | )
13 | def test_to_list(input, expected):
14 | assert to_list(input) == expected
15 |
16 |
17 | @pytest.mark.parametrize(
18 | "input, expected",
19 | [
20 | ("hello", "5d41402abc4b2a76b9719d911017c592"),
21 | ],
22 | )
23 | def test_to_md5(input, expected):
24 | assert to_md5(input) == expected
25 |
26 |
27 | @pytest.mark.parametrize(
28 | "input, expected",
29 | [
30 | (123, "123"),
31 | ([1, 2, 3], "1, 2, 3"),
32 | ({"a": 1, "b": 2}, "a: 1, b: 2"),
33 | ],
34 | )
35 | def test_to_string(input, expected):
36 | assert to_string(input) == expected
37 |
38 |
39 | @pytest.mark.parametrize(
40 | "input, expected",
41 | [
42 | ([1, 2, 2, 3], {1, 2, 3}),
43 | ("hello", {"h", "e", "l", "o"}),
44 | ],
45 | )
46 | def test_to_set(input, expected):
47 | assert to_set(input) == expected
48 |
49 |
50 | @pytest.mark.parametrize(
51 | "input, expected",
52 | [
53 | (1, True),
54 | (0, False),
55 | ("true", True),
56 | ("false", False),
57 | ("", False),
58 | ("True", True),
59 | ("False", False),
60 | ("YES", True),
61 | ("NO", False),
62 | ("Y", True),
63 | ("N", False),
64 | ("1", True),
65 | ("0", False),
66 | ("yes", True),
67 | ("no", False),
68 | ("y", True),
69 | ],
70 | )
71 | def test_to_bool(input, expected):
72 | assert to_bool(input) == expected
73 |
--------------------------------------------------------------------------------
/tests/test_date.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from usepy.date import parse, format, now
3 | from datetime import datetime
4 |
5 |
6 | @pytest.mark.parametrize(
7 | "date_string, expected",
8 | [
9 | ("2023-04-15 10:30:00", datetime(2023, 4, 15, 10, 30, 0)),
10 | ("2023-04-15", datetime(2023, 4, 15, 0, 0, 0)),
11 | ("2023-04-15 10:30", datetime(2023, 4, 15, 10, 30, 0)),
12 | ("2023-04-15 10:30:00", datetime(2023, 4, 15, 10, 30, 0)),
13 | ],
14 | )
15 | def test_parse(date_string, expected):
16 | parsed_date = parse(date_string)
17 | assert isinstance(parsed_date, datetime)
18 | assert parsed_date.year == expected.year
19 | assert parsed_date.month == expected.month
20 | assert parsed_date.day == expected.day
21 | assert parsed_date.hour == expected.hour
22 | assert parsed_date.minute == expected.minute
23 | assert parsed_date.second == expected.second
24 | assert parsed_date.microsecond == expected.microsecond
25 | assert parsed_date.tzinfo == expected.tzinfo
26 |
27 |
28 | @pytest.mark.parametrize(
29 | "date, format_str, expected",
30 | [
31 | (datetime(2023, 4, 15, 10, 30, 0), "%Y-%m-%d %H:%M:%S", "2023-04-15 10:30:00"),
32 | (datetime(2023, 4, 15, 10, 30, 0), "%Y-%m-%d", "2023-04-15"),
33 | (datetime(2023, 4, 15, 10, 30, 0), "%Y-%m-%d %H:%M", "2023-04-15 10:30"),
34 | (datetime(2023, 4, 15, 10, 30, 0), "%Y-%m-%d %H:%M:%S", "2023-04-15 10:30:00"),
35 | ],
36 | )
37 | def test_format(date, format_str, expected):
38 | assert format(date, format_str) == expected
39 |
40 |
41 | def test_now():
42 | current_time = now()
43 | assert isinstance(current_time, datetime)
44 |
--------------------------------------------------------------------------------
/tests/test_decorator.py:
--------------------------------------------------------------------------------
1 | from usepy.decorator import retry, catch_error, singleton, throttle
2 | import time
3 |
4 |
5 | def test_retry():
6 | count = 0
7 |
8 | @retry(max_attempts=3, retry_interval=0.1)
9 | def failing_function():
10 | nonlocal count
11 | count += 1
12 | if count < 3:
13 | raise ValueError("Test error")
14 | return "Success"
15 |
16 | assert failing_function() == "Success"
17 |
18 |
19 | def test_catch_error():
20 | @catch_error(return_val="Default")
21 | def error_function():
22 | raise ValueError("Test error")
23 |
24 | assert error_function() == "Default"
25 |
26 |
27 | def test_singleton():
28 | @singleton
29 | class SingletonClass:
30 | pass
31 |
32 | instance1 = SingletonClass()
33 | instance2 = SingletonClass()
34 | assert instance1 is instance2
35 |
36 |
37 | def test_throttle():
38 | call_count = 0
39 |
40 | @throttle(delay=0.1)
41 | def throttled_function():
42 | nonlocal call_count
43 | call_count += 1
44 |
45 | throttled_function()
46 | throttled_function()
47 | time.sleep(0.2)
48 | throttled_function()
49 |
50 | assert call_count == 2
51 |
--------------------------------------------------------------------------------
/tests/test_dict.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from usepy.dict import AdDict, sort_by_key, sort_by_value, merge_dicts
3 |
4 |
5 | def test_ad_dict():
6 | ad = AdDict()
7 | ad.a = 1
8 | ad["b"] = 2
9 |
10 | assert ad.a == 1
11 | assert ad["b"] == 2
12 | assert ad.get("c", 3) == 3
13 |
14 |
15 | @pytest.mark.parametrize(
16 | "input, az, expected",
17 | [
18 | ({"c": 3, "a": 1, "b": 2}, True, ["a", "b", "c"]),
19 | ({"a": 1, "b": 2, "c": 3}, False, ["c", "b", "a"]),
20 | ],
21 | )
22 | def test_sort_by_key(input, az, expected):
23 | sorted_d = sort_by_key(input, az)
24 | assert list(sorted_d.keys()) == expected
25 |
26 |
27 | @pytest.mark.parametrize(
28 | "input, az, expected",
29 | [
30 | ({"c": 3, "a": 1, "b": 2}, True, ["a", "b", "c"]),
31 | ({"a": 1, "b": 2, "c": 3}, False, ["c", "b", "a"]),
32 | ],
33 | )
34 | def test_sort_by_value(input, az, expected):
35 | sorted_d = sort_by_value(input, az)
36 | assert list(sorted_d.keys()) == expected
37 |
38 |
39 | @pytest.mark.parametrize(
40 | "dict1, dict2, expected",
41 | [
42 | ({"a": 1, "b": 2}, {"b": 3, "c": 4}, {"a": 1, "b": 3, "c": 4}),
43 | ({"a": 1, "b": 2}, {"b": 3, "c": 4, "d": 5}, {"a": 1, "b": 3, "c": 4, "d": 5}),
44 | ],
45 | )
46 | def test_merge_dicts(dict1, dict2, expected):
47 | merged = merge_dicts(dict1, dict2)
48 | assert merged == expected
49 |
--------------------------------------------------------------------------------
/tests/test_list.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from usepy.list import (
3 | chunk,
4 | count_by,
5 | compact,
6 | difference,
7 | flatten,
8 | flatten_deep,
9 | every,
10 | some,
11 | sample,
12 | shuffle,
13 | without,
14 | uniq,
15 | union,
16 | key_by,
17 | zip_tuple,
18 | zip_dict,
19 | first,
20 | last,
21 | )
22 |
23 |
24 | @pytest.mark.parametrize(
25 | "lst, size, expected",
26 | [
27 | ([1, 2, 3, 4, 5], 2, [[1, 2], [3, 4], [5]]),
28 | ([1, 2, 3, 4, 5, 6], 3, [[1, 2, 3], [4, 5, 6]]),
29 | ],
30 | )
31 | def test_chunk(lst, size, expected):
32 | assert chunk(lst, size) == expected
33 |
34 |
35 | @pytest.mark.parametrize(
36 | "lst, mapper, expected",
37 | [
38 | ([1, 2, 3, 4, 5], lambda x: x % 2 == 0, {True: 2, False: 3}),
39 | ([1, 2, 3, 4, 5, 6], lambda x: x % 3 == 0, {True: 2, False: 4}),
40 | ],
41 | )
42 | def test_count_by(lst, mapper, expected):
43 | assert count_by(lst, mapper) == expected
44 |
45 |
46 | @pytest.mark.parametrize(
47 | "lst, expected",
48 | [
49 | ([0, 1, False, 2, "", 3], [1, 2, 3]),
50 | ([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]),
51 | ],
52 | )
53 | def test_compact(lst, expected):
54 | assert compact(lst) == expected
55 |
56 |
57 | @pytest.mark.parametrize(
58 | "lst1, lst2, expected",
59 | [
60 | ([2, 1], [2, 3], [1]),
61 | ([1, 2, 3, 4, 5], [2, 3, 4], [1, 5]),
62 | ],
63 | )
64 | def test_difference(lst1, lst2, expected):
65 | assert difference(lst1, lst2) == expected
66 |
67 |
68 | @pytest.mark.parametrize(
69 | "lst, depth, expected",
70 | [
71 | ([1, [2, [3, [4]], 5]], 1, [1, 2, [3, [4]], 5]),
72 | ([1, [2, [3, [4]], 5]], 2, [1, 2, 3, [4], 5]),
73 | ([1, [2, [3, [4]], 5]], float("inf"), [1, 2, 3, 4, 5]),
74 | ],
75 | )
76 | def test_flatten(lst, depth, expected):
77 | assert flatten(lst, depth) == expected
78 |
79 |
80 | @pytest.mark.parametrize(
81 | "lst, expected",
82 | [
83 | ([1, [2, [3, [4]], 5]], [1, 2, 3, 4, 5]),
84 | ([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]),
85 | ],
86 | )
87 | def test_flatten_deep(lst, expected):
88 | assert flatten_deep(lst) == expected
89 |
90 |
91 | @pytest.mark.parametrize(
92 | "lst, fn, expected",
93 | [
94 | ([2, 4, 6], lambda x: x % 2 == 0, True),
95 | ([2, 3, 4], lambda x: x % 2 == 0, False),
96 | ],
97 | )
98 | def test_every(lst, fn, expected):
99 | assert every(lst, fn) == expected
100 |
101 |
102 | @pytest.mark.parametrize(
103 | "lst, fn, expected",
104 | [
105 | ([1, 2, 3, 4], lambda x: x % 2 == 0, True),
106 | ([1, 3, 5, 7], lambda x: x % 2 == 0, False),
107 | ],
108 | )
109 | def test_some(lst, fn, expected):
110 | assert some(lst, fn) == expected
111 |
112 |
113 | @pytest.mark.parametrize(
114 | "lst, n, expected",
115 | [
116 | ([1, 2, 3, 4, 5], 3, [1, 2, 3]),
117 | ([1, 2, 3, 4, 5], 2, [1, 2]),
118 | ],
119 | )
120 | def test_sample(lst, n, expected):
121 | sampled = sample(lst, n)
122 | assert len(sampled) == n
123 | assert all(item in lst for item in sampled)
124 |
125 |
126 | @pytest.mark.parametrize(
127 | "lst, expected",
128 | [
129 | ([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]),
130 | ([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]),
131 | ],
132 | )
133 | def test_shuffle(lst, expected):
134 | shuffled = shuffle(lst)
135 | assert sorted(shuffled) == expected
136 |
137 |
138 | @pytest.mark.parametrize(
139 | "lst, items, expected",
140 | [
141 | ([2, 1, 2, 3], 1, [2, 2, 3]),
142 | ([2, 1, 2, 3], 2, [1, 3]),
143 | ],
144 | )
145 | def test_without(lst, items, expected):
146 | assert without(lst, items) == expected
147 |
148 |
149 | @pytest.mark.parametrize(
150 | "lst, expected",
151 | [
152 | ([2, 1, 2], [1, 2]),
153 | ([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]),
154 | ],
155 | )
156 | def test_uniq(lst, expected):
157 | assert uniq(lst) == expected
158 |
159 |
160 | @pytest.mark.parametrize(
161 | "lst1, lst2, expected",
162 | [
163 | ([2], [1, 2], [1, 2]),
164 | ([1, 2, 3, 4, 5], [2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6]),
165 | ],
166 | )
167 | def test_union(lst1, lst2, expected):
168 | assert union(lst1, lst2) == expected
169 |
170 |
171 | @pytest.mark.parametrize(
172 | "lst, fn, expected",
173 | [
174 | (["a", "b", "c"], lambda x: x.upper(), {"A": "a", "B": "b", "C": "c"}),
175 | (["a", "b", "c"], lambda x: x.lower(), {"a": "a", "b": "b", "c": "c"}),
176 | ],
177 | )
178 | def test_key_by(lst, fn, expected):
179 | assert key_by(lst, fn) == expected
180 |
181 |
182 | @pytest.mark.parametrize(
183 | "lst1, lst2, expected",
184 | [
185 | (["a", "b"], [1, 2], [("a", 1), ("b", 2)]),
186 | (["a", "b"], [1, 2, 3], [("a", 1), ("b", 2), (None, 3)]),
187 | ],
188 | )
189 | def test_zip_tuple(lst1, lst2, expected):
190 | assert zip_tuple(lst1, lst2) == expected
191 |
192 |
193 | @pytest.mark.parametrize(
194 | "lst1, lst2, expected",
195 | [
196 | (["a", "b"], [1, 2], {"a": 1, "b": 2}),
197 | (["a", "b"], [1, 2, 3], {"a": 1, "b": 2}),
198 | ],
199 | )
200 | def test_zip_dict(lst1, lst2, expected):
201 | assert zip_dict(lst1, lst2) == expected
202 |
203 |
204 | @pytest.mark.parametrize(
205 | "lst, expected",
206 | [
207 | ([1, 2, 3], 1),
208 | ([], None),
209 | ],
210 | )
211 | def test_first(lst, expected):
212 | assert first(lst) == expected
213 |
214 |
215 | @pytest.mark.parametrize(
216 | "lst, expected",
217 | [
218 | ([1, 2, 3], 3),
219 | ([], None),
220 | ],
221 | )
222 | def test_last(lst, expected):
223 | assert last(lst) == expected
224 |
--------------------------------------------------------------------------------
/tests/test_misc.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from usepy.misc import get_function_name, dynamic_import
3 |
4 |
5 | def test_get_function_name():
6 |
7 | assert get_function_name() == "test_get_function_name"
8 |
9 |
10 | def test_dynamic_import():
11 | module, function = dynamic_import("usepy.list.chunk")
12 | assert function.__name__ == "chunk"
13 | assert module.__name__ == "usepy.list"
14 |
15 | with pytest.raises(ImportError):
16 | dynamic_import("non_existent_module")
17 |
18 | module, _ = dynamic_import("json")
19 | assert (
20 | module.loads(
21 | """
22 | {"a": 1, "b": 2}
23 | """
24 | )
25 | == {"a": 1, "b": 2}
26 | )
27 |
--------------------------------------------------------------------------------
/tests/test_string.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from usepy.string import (
3 | camel_case,
4 | capitalize,
5 | kebab_case,
6 | left,
7 | lower_case,
8 | middle,
9 | middle_batch,
10 | pascal_case,
11 | right,
12 | snake_case,
13 | )
14 |
15 |
16 | @pytest.mark.parametrize(
17 | "input, expected",
18 | [
19 | ("hello world", "helloWorld"),
20 | ("foo_bar", "fooBar"),
21 | ],
22 | )
23 | def test_camel_case(input, expected):
24 | assert camel_case(input) == expected
25 |
26 |
27 | @pytest.mark.parametrize(
28 | "input, expected",
29 | [
30 | ("hello", "Hello"),
31 | ("WORLD", "World"),
32 | ],
33 | )
34 | def test_capitalize(input, expected):
35 | assert capitalize(input) == expected
36 |
37 |
38 | @pytest.mark.parametrize(
39 | "input, expected",
40 | [
41 | ("hello world", "hello-world"),
42 | ("fooBar", "foo-bar"),
43 | ],
44 | )
45 | def test_kebab_case(input, expected):
46 | assert kebab_case(input) == expected
47 |
48 |
49 | @pytest.mark.parametrize(
50 | "input, end, expected",
51 | [
52 | ("hello", "lo", "hel"),
53 | ("python", "on", "pyth"),
54 | ],
55 | )
56 | def test_left(input, end, expected):
57 | assert left(input, end) == expected
58 |
59 |
60 | @pytest.mark.parametrize(
61 | "input, expected",
62 | [
63 | ("HELLO", "hello"),
64 | ("World", "world"),
65 | ],
66 | )
67 | def test_lower_case(input, expected):
68 | assert lower_case(input) == expected
69 |
70 |
71 | @pytest.mark.parametrize(
72 | "input, start, end, expected",
73 | [
74 | ("python", "py", "on", "th"),
75 | ("hello", "hel", "o", "l"),
76 | ],
77 | )
78 | def test_middle(input, start, end, expected):
79 | assert middle(input, start, end) == expected
80 |
81 |
82 | @pytest.mark.parametrize(
83 | "input, start, end, expected",
84 | [
85 | ("a1b2a3b4a", "a", "b", ["1", "3"]),
86 | ],
87 | )
88 | def test_middle_batch(input, start, end, expected):
89 | assert middle_batch(input, start, end) == expected
90 |
91 |
92 | @pytest.mark.parametrize(
93 | "input, expected",
94 | [
95 | ("hello world", "HelloWorld"),
96 | ("foo_bar", "FooBar"),
97 | ],
98 | )
99 | def test_pascal_case(input, expected):
100 | assert pascal_case(input) == expected
101 |
102 |
103 | @pytest.mark.parametrize(
104 | "input, end, expected",
105 | [
106 | ("hello", "l", "lo"),
107 | ("python", "th", "on"),
108 | ],
109 | )
110 | def test_right(input, end, expected):
111 | assert right(input, end) == expected
112 |
113 |
114 | @pytest.mark.parametrize(
115 | "input, expected",
116 | [
117 | ("helloWorld", "hello_world"),
118 | ("foo-bar", "foo_bar"),
119 | ],
120 | )
121 | def test_snake_case(input, expected):
122 | assert snake_case(input) == expected
123 |
--------------------------------------------------------------------------------
/tests/test_validator.py:
--------------------------------------------------------------------------------
1 | from usepy.validator import is_async_function, is_url
2 |
3 |
4 | def test_is_async_function():
5 | async def async_func():
6 | pass
7 |
8 | def sync_func():
9 | pass
10 |
11 | assert is_async_function(async_func)
12 | assert not is_async_function(sync_func)
13 |
14 |
15 | def test_is_url():
16 | assert is_url("https://www.example.com")
17 | assert is_url("http://localhost:8000")
18 | assert not is_url("not a url")
19 | assert not is_url("mailto:user@example.com")
20 | assert not is_url("ftp://invalid-scheme.com")
21 |
--------------------------------------------------------------------------------