├── .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 |
2 | logo 3 | 4 | `usepy`是一个简单易用的Python工具库,包含了一些常用的工具函数。 5 | 6 | 7 | Test 8 | 9 | 10 | Downloads 11 | 12 | Package version 13 | 14 | 15 | 16 | Supported Python versions 17 | 18 |
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 | 21 | 22 | -------------------------------------------------------------------------------- /docs/.vitepress/components/CourseLink.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /docs/.vitepress/components/FeaturesList.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 20 | -------------------------------------------------------------------------------- /docs/.vitepress/components/HomePage.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 42 | -------------------------------------------------------------------------------- /docs/.vitepress/components/ListItem.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 57 | 58 | 63 | -------------------------------------------------------------------------------- /docs/.vitepress/components/ModuleInfo.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /docs/.vitepress/components/ProjectInfo.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 29 | 30 | 32 | -------------------------------------------------------------------------------- /docs/.vitepress/components/TagPage.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 31 | 44 | -------------------------------------------------------------------------------- /docs/.vitepress/components/Tags.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 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 | ![](https://miclon-job.oss-cn-hangzhou.aliyuncs.com/img/20230228222300.png) 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 | ![](https://miclon-job.oss-cn-hangzhou.aliyuncs.com/img/20230228223646.png) 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 | ![](https://miclon-job.oss-cn-hangzhou.aliyuncs.com/img/20230228222300.png) 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 | ![](https://miclon-job.oss-cn-hangzhou.aliyuncs.com/img/20230228223646.png) 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 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | 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 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | 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 | --------------------------------------------------------------------------------