sqlym 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. sqlym-0.1.0/.claude/settings.json +5 -0
  2. sqlym-0.1.0/.devcontainer/devcontainer.json +63 -0
  3. sqlym-0.1.0/.github/workflows/ci.yml +124 -0
  4. sqlym-0.1.0/.gitignore +134 -0
  5. sqlym-0.1.0/.pre-commit-config.yaml +11 -0
  6. sqlym-0.1.0/.python-version +1 -0
  7. sqlym-0.1.0/.vscode/settings.json +3 -0
  8. sqlym-0.1.0/CLAUDE.md +80 -0
  9. sqlym-0.1.0/LICENSE +21 -0
  10. sqlym-0.1.0/Makefile +48 -0
  11. sqlym-0.1.0/PKG-INFO +260 -0
  12. sqlym-0.1.0/README.ja.md +232 -0
  13. sqlym-0.1.0/README.md +233 -0
  14. sqlym-0.1.0/SQL_SYNTAX.ja.md +461 -0
  15. sqlym-0.1.0/SQL_SYNTAX.md +467 -0
  16. sqlym-0.1.0/docker-compose.yml +43 -0
  17. sqlym-0.1.0/docs/DESIGN.md +747 -0
  18. sqlym-0.1.0/docs/SPEC.md +350 -0
  19. sqlym-0.1.0/docs/TASK.M1.md +304 -0
  20. sqlym-0.1.0/pyproject.toml +113 -0
  21. sqlym-0.1.0/src/sqlym/__init__.py +28 -0
  22. sqlym-0.1.0/src/sqlym/_parse.py +36 -0
  23. sqlym-0.1.0/src/sqlym/config.py +7 -0
  24. sqlym-0.1.0/src/sqlym/dialect.py +81 -0
  25. sqlym-0.1.0/src/sqlym/escape_utils.py +48 -0
  26. sqlym-0.1.0/src/sqlym/exceptions.py +17 -0
  27. sqlym-0.1.0/src/sqlym/loader.py +72 -0
  28. sqlym-0.1.0/src/sqlym/mapper/__init__.py +7 -0
  29. sqlym-0.1.0/src/sqlym/mapper/column.py +40 -0
  30. sqlym-0.1.0/src/sqlym/mapper/dataclass.py +97 -0
  31. sqlym-0.1.0/src/sqlym/mapper/factory.py +50 -0
  32. sqlym-0.1.0/src/sqlym/mapper/manual.py +21 -0
  33. sqlym-0.1.0/src/sqlym/mapper/protocol.py +20 -0
  34. sqlym-0.1.0/src/sqlym/mapper/pydantic.py +23 -0
  35. sqlym-0.1.0/src/sqlym/parser/__init__.py +0 -0
  36. sqlym-0.1.0/src/sqlym/parser/line_unit.py +36 -0
  37. sqlym-0.1.0/src/sqlym/parser/tokenizer.py +117 -0
  38. sqlym-0.1.0/src/sqlym/parser/twoway.py +516 -0
  39. sqlym-0.1.0/tests/__init__.py +0 -0
  40. sqlym-0.1.0/tests/conftest.py +159 -0
  41. sqlym-0.1.0/tests/integration/test_mysql.py +422 -0
  42. sqlym-0.1.0/tests/integration/test_oracle.py +534 -0
  43. sqlym-0.1.0/tests/integration/test_postgresql.py +424 -0
  44. sqlym-0.1.0/tests/integration/test_sqlite.py +438 -0
  45. sqlym-0.1.0/tests/test_column_entity.py +133 -0
  46. sqlym-0.1.0/tests/test_create_mapper.py +158 -0
  47. sqlym-0.1.0/tests/test_dataclass_mapper.py +295 -0
  48. sqlym-0.1.0/tests/test_dialect.py +167 -0
  49. sqlym-0.1.0/tests/test_exceptions.py +49 -0
  50. sqlym-0.1.0/tests/test_line_unit.py +95 -0
  51. sqlym-0.1.0/tests/test_mapper_protocol.py +153 -0
  52. sqlym-0.1.0/tests/test_public_api.py +175 -0
  53. sqlym-0.1.0/tests/test_pydantic_mapper.py +128 -0
  54. sqlym-0.1.0/tests/test_sql_loader.py +217 -0
  55. sqlym-0.1.0/tests/test_tokenizer.py +172 -0
  56. sqlym-0.1.0/tests/test_twoway_clean_sql.py +149 -0
  57. sqlym-0.1.0/tests/test_twoway_in_clause.py +252 -0
  58. sqlym-0.1.0/tests/test_twoway_param_substitution.py +125 -0
  59. sqlym-0.1.0/tests/test_twoway_parse_lines.py +173 -0
  60. sqlym-0.1.0/tests/test_twoway_placeholder.py +282 -0
  61. sqlym-0.1.0/tests/test_twoway_removal.py +201 -0
  62. sqlym-0.1.0/uv.lock +923 -0
@@ -0,0 +1,5 @@
1
+ {
2
+ "enabledPlugins": {
3
+ "superpowers@claude-plugins-official": true
4
+ }
5
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "sqly",
3
+ "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
4
+ "containerEnv": {
5
+ "PYTHONUNBUFFERED": "1",
6
+ "PYTHONDONTWRITEBYTECODE": "1",
7
+ "UV_CACHE_DIR": "/home/vscode/.cache/uv",
8
+ "UV_LINK_MODE": "copy",
9
+ "UV_PROJECT_ENVIRONMENT": "/home/vscode/.venv",
10
+ "UV_COMPILE_BYTECODE": "1",
11
+ "CLAUDE_CONFIG_DIR": "/home/vscode/.claude",
12
+ "SQLY_TEST_POSTGRESQL_URL": "host=localhost port=5432 dbname=sqly_test user=sqly password=sqly_test_pass",
13
+ "SQLY_TEST_MYSQL_URL": "mysql://sqly:sqly_test_pass@localhost:3306/sqly_test",
14
+ "SQLY_TEST_ORACLE_DSN": "localhost:1521/XEPDB1"
15
+ },
16
+ "features": {
17
+ "ghcr.io/devcontainers/features/github-cli:1": {},
18
+ "ghcr.io/devcontainers/features/common-utils:2": {
19
+ "configureZshAsDefaultShell": true
20
+ },
21
+ "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
22
+ "packages": "curl,wget,git,jq,ca-certificates,build-essential,ripgrep,fd-find"
23
+ },
24
+ "ghcr.io/va-h/devcontainers-features/uv:1": {
25
+ "shellAutocompletion": true
26
+ },
27
+ "ghcr.io/devcontainers/features/node:1": {},
28
+ "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}
29
+ },
30
+ "runArgs": [
31
+ "--init",
32
+ "--rm"
33
+ ],
34
+ "hostRequirements": {
35
+ "gpu": "optional"
36
+ },
37
+ "customizations": {
38
+ "vscode": {
39
+ "settings": {
40
+ "python.defaultInterpreterPath": "/home/vscode/.venv/bin/python",
41
+ "python.testing.pytestEnabled": true
42
+ },
43
+ "extensions": [
44
+ "ms-python.python",
45
+ "charliermarsh.ruff",
46
+ "eamodio.gitlens",
47
+ "tamasfe.even-better-toml",
48
+ "yzhang.markdown-all-in-one"
49
+ ]
50
+ }
51
+ },
52
+ "remoteUser": "vscode",
53
+ "containerUser": "vscode",
54
+ "forwardPorts": [5432, 3306, 1521],
55
+ "initializeCommand": "mkdir -p ${localEnv:HOME}/.claude && touch ${localEnv:HOME}/.claude/CLAUDE.md && docker compose up -d --wait postgresql mysql oracle",
56
+ "mounts": [
57
+ "source=shell_history-${devcontainerId},target=/shell_history,type=volume",
58
+ "source=${localEnv:HOME}/.claude,target=/home/vscode/.claude,type=bind",
59
+ "source=${localEnv:HOME}/.claude/CLAUDE.md,target=/home/vscode/.claude/CLAUDE.md,type=bind"
60
+ ],
61
+ "postCreateCommand": "uv sync --dev",
62
+ "postStartCommand": "uv run pre-commit install"
63
+ }
@@ -0,0 +1,124 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.13"
16
+ - uses: astral-sh/setup-uv@v5
17
+ - run: uv sync --dev
18
+ - run: uv run ruff check src/ tests/
19
+ - run: uv run ruff format --check src/ tests/
20
+
21
+ test-sqlite:
22
+ runs-on: ubuntu-latest
23
+ strategy:
24
+ matrix:
25
+ python-version: ["3.10", "3.13"]
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ - uses: actions/setup-python@v5
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+ - uses: astral-sh/setup-uv@v5
32
+ - run: uv sync --dev
33
+ - run: uv run pytest -m "not postgresql and not mysql and not oracle" -v
34
+
35
+ test-postgresql:
36
+ runs-on: ubuntu-latest
37
+ strategy:
38
+ matrix:
39
+ python-version: ["3.10", "3.13"]
40
+ services:
41
+ postgres:
42
+ image: postgres:16-alpine
43
+ env:
44
+ POSTGRES_USER: sqly
45
+ POSTGRES_PASSWORD: sqly_test_pass
46
+ POSTGRES_DB: sqly_test
47
+ ports:
48
+ - 5432:5432
49
+ options: >-
50
+ --health-cmd "pg_isready -U sqly -d sqly_test"
51
+ --health-interval 5s
52
+ --health-timeout 5s
53
+ --health-retries 5
54
+ env:
55
+ SQLY_TEST_POSTGRESQL_URL: postgresql://sqly:sqly_test_pass@localhost:5432/sqly_test
56
+ steps:
57
+ - uses: actions/checkout@v4
58
+ - uses: actions/setup-python@v5
59
+ with:
60
+ python-version: ${{ matrix.python-version }}
61
+ - uses: astral-sh/setup-uv@v5
62
+ - run: uv sync --dev
63
+ - run: uv run pytest -m postgresql -v
64
+
65
+ test-mysql:
66
+ runs-on: ubuntu-latest
67
+ strategy:
68
+ matrix:
69
+ python-version: ["3.10", "3.13"]
70
+ services:
71
+ mysql:
72
+ image: mysql:8.0
73
+ env:
74
+ MYSQL_ROOT_PASSWORD: sqly_test_pass
75
+ MYSQL_USER: sqly
76
+ MYSQL_PASSWORD: sqly_test_pass
77
+ MYSQL_DATABASE: sqly_test
78
+ ports:
79
+ - 3306:3306
80
+ options: >-
81
+ --health-cmd "mysqladmin ping -h localhost -u sqly -psqly_test_pass"
82
+ --health-interval 5s
83
+ --health-timeout 5s
84
+ --health-retries 5
85
+ env:
86
+ SQLY_TEST_MYSQL_URL: mysql+pymysql://sqly:sqly_test_pass@localhost:3306/sqly_test
87
+ steps:
88
+ - uses: actions/checkout@v4
89
+ - uses: actions/setup-python@v5
90
+ with:
91
+ python-version: ${{ matrix.python-version }}
92
+ - uses: astral-sh/setup-uv@v5
93
+ - run: uv sync --dev
94
+ - run: uv run pytest -m mysql -v
95
+
96
+ test-oracle:
97
+ runs-on: ubuntu-latest
98
+ strategy:
99
+ matrix:
100
+ python-version: ["3.10", "3.13"]
101
+ services:
102
+ oracle:
103
+ image: gvenzl/oracle-xe:21-slim
104
+ env:
105
+ ORACLE_PASSWORD: sqly_test_pass
106
+ APP_USER: sqly
107
+ APP_USER_PASSWORD: sqly_test_pass
108
+ ports:
109
+ - 1521:1521
110
+ options: >-
111
+ --health-cmd "healthcheck.sh"
112
+ --health-interval 10s
113
+ --health-timeout 5s
114
+ --health-retries 10
115
+ env:
116
+ SQLY_TEST_ORACLE_DSN: localhost:1521/XEPDB1
117
+ steps:
118
+ - uses: actions/checkout@v4
119
+ - uses: actions/setup-python@v5
120
+ with:
121
+ python-version: ${{ matrix.python-version }}
122
+ - uses: astral-sh/setup-uv@v5
123
+ - run: uv sync --dev
124
+ - run: uv run pytest -m oracle -v
sqlym-0.1.0/.gitignore ADDED
@@ -0,0 +1,134 @@
1
+ # Claude Code
2
+ .claude/settings.local.json
3
+
4
+ # Created by https://www.toptal.com/developers/gitignore/api/linux,macos,visualstudiocode,vim,python,jupyternotebooks,venv,virtualenv
5
+
6
+ ### JupyterNotebooks ###
7
+ .ipynb_checkpoints
8
+ */.ipynb_checkpoints/*
9
+ profile_default/
10
+ ipython_config.py
11
+
12
+ ### Linux ###
13
+ *~
14
+ .fuse_hidden*
15
+ .directory
16
+ .Trash-*
17
+ .nfs*
18
+
19
+ ### macOS ###
20
+ .DS_Store
21
+ .AppleDouble
22
+ .LSOverride
23
+ Icon
24
+ ._*
25
+ .DocumentRevisions-V100
26
+ .fseventsd
27
+ .Spotlight-V100
28
+ .TemporaryItems
29
+ .Trashes
30
+ .VolumeIcon.icns
31
+ .com.apple.timemachine.donotpresent
32
+ .AppleDB
33
+ .AppleDesktop
34
+ Network Trash Folder
35
+ Temporary Items
36
+ .apdisk
37
+ *.icloud
38
+
39
+ ### Python ###
40
+ __pycache__/
41
+ *.py[cod]
42
+ *$py.class
43
+ *.so
44
+ .Python
45
+ build/
46
+ develop-eggs/
47
+ dist/
48
+ downloads/
49
+ eggs/
50
+ .eggs/
51
+ lib/
52
+ lib64/
53
+ parts/
54
+ sdist/
55
+ var/
56
+ wheels/
57
+ share/python-wheels/
58
+ *.egg-info/
59
+ .installed.cfg
60
+ *.egg
61
+ MANIFEST
62
+ *.manifest
63
+ *.spec
64
+ pip-log.txt
65
+ pip-delete-this-directory.txt
66
+
67
+ # Unit test / coverage reports
68
+ htmlcov/
69
+ .tox/
70
+ .nox/
71
+ .coverage
72
+ .coverage.*
73
+ .cache
74
+ nosetests.xml
75
+ coverage.xml
76
+ *.cover
77
+ *.py,cover
78
+ .hypothesis/
79
+ .pytest_cache/
80
+ cover/
81
+
82
+ # Translations
83
+ *.mo
84
+ *.pot
85
+
86
+ # Environments
87
+ .env
88
+ .venv
89
+ env/
90
+ venv/
91
+ ENV/
92
+ env.bak/
93
+ venv.bak/
94
+
95
+ # Packaging
96
+ __pypackages__/
97
+ .pdm.toml
98
+ poetry.toml
99
+
100
+ # Type checkers
101
+ .mypy_cache/
102
+ .dmypy.json
103
+ dmypy.json
104
+ .pyre/
105
+ .pytype/
106
+ cython_debug/
107
+ pyrightconfig.json
108
+
109
+ # Ruff
110
+ .ruff_cache/
111
+
112
+ ### Vim ###
113
+ [._]*.s[a-v][a-z]
114
+ !*.svg
115
+ [._]*.sw[a-p]
116
+ [._]s[a-rt-v][a-z]
117
+ [._]ss[a-gi-z]
118
+ [._]sw[a-p]
119
+ Session.vim
120
+ Sessionx.vim
121
+ .netrwhist
122
+ tags
123
+ [._]*.un~
124
+
125
+ ### VisualStudioCode ###
126
+ .vscode/*
127
+ !.vscode/settings.json
128
+ !.vscode/tasks.json
129
+ !.vscode/launch.json
130
+ !.vscode/extensions.json
131
+ !.vscode/*.code-snippets
132
+ .history/
133
+ *.vsix
134
+ .ionide
@@ -0,0 +1,11 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/uv-pre-commit
3
+ rev: 0.8.22
4
+ hooks:
5
+ - id: uv-lock
6
+ - repo: https://github.com/astral-sh/ruff-pre-commit
7
+ rev: v0.13.2
8
+ hooks:
9
+ - id: ruff-check
10
+ args: [--fix]
11
+ - id: ruff-format
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,3 @@
1
+ {
2
+ "makefile.configureOnOpen": false
3
+ }
sqlym-0.1.0/CLAUDE.md ADDED
@@ -0,0 +1,80 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ sqly is a SQL-first database access library for Python, inspired by Java's Clione-SQL and Doma2. It provides 2way SQL parsing (SQL files remain directly executable by DB tools) and flexible row-to-object mapping.
8
+
9
+ - **Language**: Python 3.10+
10
+ - **Dependencies**: None (stdlib only); Pydantic is optional
11
+ - **License**: MIT
12
+ - **Status**: v1.0 implementation complete
13
+
14
+ ## Architecture
15
+
16
+ ```
17
+ src/sqlym/
18
+ ├── __init__.py # Public API (parse_sql, create_mapper, Column, entity, etc.)
19
+ ├── _parse.py # parse_sql convenience function
20
+ ├── config.py # Error message settings
21
+ ├── dialect.py # Dialect enum (SQLITE, POSTGRESQL, MYSQL, ORACLE)
22
+ ├── escape_utils.py # escape_like utility for LIKE clause escaping
23
+ ├── exceptions.py # SqlyError, SqlParseError, MappingError, SqlFileNotFoundError
24
+ ├── loader.py # SqlLoader: file-based SQL template loading (with dialect support)
25
+ ├── parser/
26
+ │ ├── tokenizer.py # SQL tokenizer
27
+ │ ├── line_unit.py # LineUnit: line-level processing unit
28
+ │ └── twoway.py # TwoWaySQLParser: Clione-SQL 2way SQL engine
29
+ └── mapper/
30
+ ├── protocol.py # RowMapper Protocol (runtime_checkable)
31
+ ├── dataclass.py # DataclassMapper (auto-mapping with caching)
32
+ ├── pydantic.py # PydanticMapper (optional)
33
+ ├── column.py # Column annotation & @entity decorator
34
+ ├── manual.py # ManualMapper
35
+ └── factory.py # create_mapper factory function
36
+ ```
37
+
38
+ ### Core Concepts
39
+
40
+ **Clione-SQL 4 Rules** govern the parser:
41
+ 1. SQL is processed line-by-line (LineUnit)
42
+ 2. Indentation defines parent/child relationships
43
+ 3. If all children are removed, parent is also removed (bottom-up propagation)
44
+ 4. `$`-prefixed parameters that are None cause line removal
45
+
46
+ **Parameter syntax in SQL comments**:
47
+ - `/* $name */default` — removable (None removes the line)
48
+ - `/* name */default` — non-removable (None binds as NULL)
49
+ - `IN /* $ids */(1,2,3)` — auto-expands list parameters
50
+
51
+ **Parser pipeline**: parse lines → build tree from indentation → evaluate params & mark removal → propagate removal upward → rebuild SQL → clean dangling WHERE/AND/OR/empty parens
52
+
53
+ **Mapper hierarchy** (via `create_mapper()` factory):
54
+ - `DataclassMapper` — auto-maps using field introspection with caching
55
+ - `PydanticMapper` — auto-detected via `model_validate()`
56
+ - `ManualMapper` — wraps user-provided function/lambda
57
+
58
+ **Column mapping priority**: `Annotated[T, Column("X")]` > `@entity(column_map={})` > `@entity(naming="...")` > field name as-is
59
+
60
+ ### Placeholder formats
61
+ Supports `?` (SQLite/JDBC), `%s` (psycopg2), `:name` (Oracle) — configurable per parser instance.
62
+
63
+ ## Implementation Phases (from DESIGN.md)
64
+
65
+ 1. Parser foundation (LineUnit, line parsing, basic parameter substitution)
66
+ 2. Parser completion (line removal, IN clause expansion, SQL cleanup)
67
+ 3. Mappers (RowMapper, DataclassMapper, Column/entity)
68
+ 4. Integration (SqlLoader, public API)
69
+
70
+ ## Key Design Decisions
71
+
72
+ - Zero external dependencies for core — Pydantic support is optional and lazy-imported
73
+ - Protocol-based mapper interface with `@runtime_checkable` for duck typing
74
+ - Class-level mapping cache in DataclassMapper for performance
75
+ - Indentation carries semantic meaning (parent/child relationships for cascading removal)
76
+
77
+ ## Specifications
78
+
79
+ - `docs/SPEC.md` — Functional specification (Japanese)
80
+ - `docs/DESIGN.md` — Implementation design with class-level details (Japanese)
sqlym-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 izuno4t
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
sqlym-0.1.0/Makefile ADDED
@@ -0,0 +1,48 @@
1
+ .PHONY: install test lint format lint-fix pre-commit clean db-up db-down test-postgresql test-mysql test-oracle test-db test-all
2
+
3
+ install:
4
+ uv sync --dev
5
+
6
+ test:
7
+ uv run pytest
8
+
9
+ test-cov:
10
+ uv run pytest --cov=sqly --cov-report=term-missing
11
+
12
+ lint:
13
+ uv run ruff check .
14
+
15
+ format:
16
+ uv run ruff format .
17
+
18
+ lint-fix:
19
+ uv run ruff check . --fix
20
+
21
+ pre-commit:
22
+ uv run pre-commit run --all-files
23
+
24
+ db-up:
25
+ docker compose up -d --wait
26
+
27
+ db-down:
28
+ docker compose down
29
+
30
+ test-postgresql:
31
+ uv run pytest -m postgresql -v
32
+
33
+ test-mysql:
34
+ uv run pytest -m mysql -v
35
+
36
+ test-oracle:
37
+ uv run pytest -m oracle -v
38
+
39
+ test-db:
40
+ uv run pytest -m "postgresql or mysql or oracle" -v
41
+
42
+ test-all:
43
+ uv run pytest -v
44
+
45
+ clean:
46
+ rm -rf .venv .ruff_cache .pytest_cache .coverage htmlcov dist build
47
+ find . -type d -name __pycache__ -exec rm -rf {} +
48
+ find . -type d -name "*.egg-info" -exec rm -rf {} +