winidjango 1.0.4__py3-none-any.whl → 2.0.55__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
- """Database utilities for Django.
1
+ """Database utilities and lightweight model helpers.
2
2
 
3
- This module provides utility functions for working with Django models,
4
- including hashing, topological sorting, and database operations.
5
- These utilities help with efficient and safe database interactions.
3
+ This module provides helpers used across the project when manipulating
4
+ Django models: ordering models by foreign-key dependencies, creating a
5
+ deterministic hash for unsaved instances, and a project-wide
6
+ ``BaseModel`` that exposes common timestamp fields.
6
7
  """
7
8
 
8
9
  from datetime import datetime
@@ -84,23 +85,26 @@ def hash_model_instance(
84
85
  instance: Model,
85
86
  fields: "list[Field[Any, Any] | ForeignObjectRel | GenericForeignKey]",
86
87
  ) -> int:
87
- """Hash a model instance based on its field values.
88
+ """Compute a deterministic hash for a model instance.
88
89
 
89
- Generates a hash for a Django model instance by considering the values
90
- of its fields. This can be useful for comparing instances, especially
91
- when dealing with related objects or complex data structures. The hash
92
- is generated by recursively hashing related objects up to a specified
93
- depth.
94
- This is not very reliable, use with caution.
95
- Only use if working with unsafed objects or bulks, as with safed
90
+ The function returns a hash suitable for comparing unsaved model
91
+ instances by their field values. If the instance has a primary key
92
+ (``instance.pk``) that key is hashed and returned immediately; this
93
+ keeps comparisons cheap for persisted objects.
96
94
 
97
95
  Args:
98
- instance (Model): The Django model instance to hash
99
- fields (list[str]): The fields to hash
96
+ instance (Model): The Django model instance to hash.
97
+ fields (list[Field | ForeignObjectRel | GenericForeignKey]):
98
+ Field objects that should be included when computing the hash.
100
99
 
101
100
  Returns:
102
- int: The hash value representing the instance's data
101
+ int: Deterministic integer hash of the instance. For persisted
102
+ instances this is ``hash(instance.pk)``.
103
103
 
104
+ Notes:
105
+ - The returned hash is intended for heuristic comparisons (e.g.
106
+ deduplication in import pipelines) and is not cryptographically
107
+ secure. Use with care when relying on absolute uniqueness.
104
108
  """
105
109
  if instance.pk:
106
110
  return hash(instance.pk)
@@ -113,9 +117,11 @@ def hash_model_instance(
113
117
 
114
118
 
115
119
  class BaseModel(Model):
116
- """Base model for all models in the project.
120
+ """Abstract base model containing common fields and helpers.
117
121
 
118
- Provides common fields and methods for all models.
122
+ Concrete models can inherit from this class to get consistent
123
+ ``created_at`` and ``updated_at`` timestamp fields and convenient
124
+ string representations.
119
125
  """
120
126
 
121
127
  created_at: DateTimeField[datetime, datetime] = DateTimeField(auto_now_add=True)
@@ -128,10 +134,13 @@ class BaseModel(Model):
128
134
  abstract = True
129
135
 
130
136
  def __str__(self) -> str:
131
- """Base string representation of a model.
137
+ """Return a concise human-readable representation.
138
+
139
+ The default shows the model class name and primary key which is
140
+ useful for logging and interactive debugging.
132
141
 
133
142
  Returns:
134
- str: The string representation of the model as all fields and their values.
143
+ str: Short representation, e.g. ``MyModel(123)``.
135
144
  """
136
145
  return f"{self.__class__.__name__}({self.pk})"
137
146
 
@@ -141,5 +150,9 @@ class BaseModel(Model):
141
150
 
142
151
  @property
143
152
  def meta(self) -> "Options[Self]":
144
- """Get the meta options for the model."""
153
+ """Return the model's ``_meta`` options object.
154
+
155
+ This property is a small convenience wrapper used to make access
156
+ sites slightly more explicit in code and improve typing in callers.
157
+ """
145
158
  return self._meta
winidjango/src/db/sql.py CHANGED
@@ -1,4 +1,14 @@
1
- """Module for database operations with sql."""
1
+ """Low-level helper to execute raw SQL against Django's database.
2
+
3
+ This module exposes :func:`execute_sql` which runs a parameterized SQL
4
+ query using Django's database connection and returns column names and
5
+ rows. It is intended for one-off queries where ORM abstractions are
6
+ insufficient or when reading complex reports from the database.
7
+
8
+ The helper uses Django's connection cursor context manager to ensure
9
+ resources are cleaned up correctly. Results are fetched into memory so
10
+ avoid using it for very large result sets.
11
+ """
2
12
 
3
13
  from typing import Any
4
14
 
@@ -7,51 +17,23 @@ from django.db import connection
7
17
 
8
18
  def execute_sql(
9
19
  sql: str, params: dict[str, Any] | None = None
10
- ) -> tuple[list[str], list[Any]]:
11
- """Execute raw SQL query and return column names with results.
12
-
13
- Executes a raw SQL query using Django's database connection and returns
14
- both the column names and the result rows. This provides a convenient
15
- way to run custom SQL queries while maintaining Django's database
16
- connection management and parameter binding for security.
17
-
18
- The function automatically handles cursor management and ensures proper
19
- cleanup of database resources. Parameters are safely bound to prevent
20
- SQL injection attacks.
20
+ ) -> tuple[list[str], list[tuple[Any, ...]]]:
21
+ """Execute a SQL statement and return column names and rows.
21
22
 
22
23
  Args:
23
- sql (str): The SQL query string to execute. Can contain parameter
24
- placeholders that will be safely bound using the params argument.
25
- params (dict[str, Any] | None, optional): Dictionary of parameters
26
- to bind to the SQL query for safe parameter substitution.
27
- Defaults to None if no parameters are needed.
24
+ sql (str): SQL statement possibly containing named placeholders
25
+ (``%(name)s``) for database binding.
26
+ params (dict[str, Any] | None): Optional mapping of parameters to
27
+ bind to the query.
28
28
 
29
29
  Returns:
30
- tuple[list[str], list[Any]]: A tuple containing:
31
- - list[str]: Column names from the query result
32
- - list[Any]: List of result rows, where each row is a tuple
33
- of values corresponding to the column names
34
- Empty list if no results are returned
30
+ Tuple[List[str], List[Tuple[Any, ...]]]: A tuple where the first
31
+ element is the list of column names (empty list if the statement
32
+ returned no rows) and the second element is a list of row tuples.
35
33
 
36
34
  Raises:
37
- django.db.Error: If there's a database error during query execution
38
- django.db.ProgrammingError: If the SQL syntax is invalid
39
- django.db.IntegrityError: If the query violates database constraints
40
-
41
- Example:
42
- >>> sql = "SELECT id, username FROM auth_user WHERE is_active = %(active)s"
43
- >>> params = {"active": True}
44
- >>> columns, rows = execute_sql(sql, params)
45
- >>> columns
46
- ['id', 'username']
47
- >>> rows[0]
48
- (1, 'admin')
49
-
50
- Note:
51
- - Uses Django's default database connection
52
- - Automatically manages cursor lifecycle
53
- - Parameters are safely bound to prevent SQL injection
54
- - Returns all results in memory - use with caution for large datasets
35
+ django.db.Error: Propagates underlying database errors raised by
36
+ Django's database backend.
55
37
  """
56
38
  with connection.cursor() as cursor:
57
39
  cursor.execute(sql=sql, params=params)
@@ -0,0 +1,286 @@
1
+ Metadata-Version: 2.4
2
+ Name: winidjango
3
+ Version: 2.0.55
4
+ Summary: A utils package for django
5
+ Keywords:
6
+ Author: Winipedia
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Typing :: Typed
14
+ Requires-Dist: django
15
+ Requires-Dist: django-stubs-ext
16
+ Requires-Dist: winiutils
17
+ Maintainer: Winipedia
18
+ Requires-Python: >=3.12
19
+ Project-URL: Changelog, https://github.com/Winipedia/winidjango/releases
20
+ Project-URL: Documentation, https://Winipedia.github.io/winidjango
21
+ Project-URL: Homepage, https://github.com/Winipedia/winidjango
22
+ Project-URL: Issues, https://github.com/Winipedia/winidjango/issues
23
+ Project-URL: Source, https://github.com/Winipedia/winidjango
24
+ Description-Content-Type: text/markdown
25
+
26
+ # winidjango
27
+
28
+ <!-- tooling -->
29
+ [![pyrig](https://img.shields.io/badge/built%20with-pyrig-3776AB?logo=buildkite&logoColor=black)](https://github.com/Winipedia/pyrig)
30
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
31
+ [![Container](https://img.shields.io/badge/Container-Podman-A23CD6?logo=podman&logoColor=grey&colorA=0D1F3F&colorB=A23CD6)](https://podman.io/)
32
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://pre-commit.com/)
33
+ [![MkDocs](https://img.shields.io/badge/MkDocs-Documentation-326CE5?logo=mkdocs&logoColor=white)](https://www.mkdocs.org/)
34
+ <!-- code-quality -->
35
+ [![ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
36
+ [![ty](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json)](https://github.com/astral-sh/ty)
37
+ [![security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)
38
+ [![pytest](https://img.shields.io/badge/tested%20with-pytest-46a2f1.svg?logo=pytest)](https://pytest.org/)
39
+ [![codecov](https://codecov.io/gh/Winipedia/winidjango/branch/main/graph/badge.svg)](https://codecov.io/gh/Winipedia/winidjango)
40
+ [![rumdl](https://img.shields.io/badge/markdown-rumdl-darkgreen)](https://github.com/rvben/rumdl)
41
+ <!-- package-info -->
42
+ [![PyPI](https://img.shields.io/pypi/v/winidjango?logo=pypi&logoColor=white)](https://pypi.org/project/winidjango)
43
+ [![Python](https://img.shields.io/badge/python-3.12|3.13|3.14-blue.svg?logo=python&logoColor=white)](https://www.python.org/)
44
+ [![License](https://img.shields.io/github/license/Winipedia/winidjango)](https://github.com/Winipedia/winidjango/blob/main/LICENSE)
45
+ <!-- ci/cd -->
46
+ [![CI](https://img.shields.io/github/actions/workflow/status/Winipedia/winidjango/health_check.yaml?label=CI&logo=github)](https://github.com/Winipedia/winidjango/actions/workflows/health_check.yaml)
47
+ [![CD](https://img.shields.io/github/actions/workflow/status/Winipedia/winidjango/release.yaml?label=CD&logo=github)](https://github.com/Winipedia/winidjango/actions/workflows/release.yaml)
48
+ <!-- documentation -->
49
+ [![Documentation](https://img.shields.io/badge/Docs-GitHub%20Pages-black?style=for-the-badge&logo=github&logoColor=white)](https://Winipedia.github.io/winidjango)
50
+
51
+ ---
52
+
53
+ > A utils package for django
54
+
55
+ ---
56
+
57
+ ## Table of Contents
58
+
59
+ - [Features](#features)
60
+ - [Installation](#installation)
61
+ - [Quick Start](#quick-start)
62
+ - [Documentation](#documentation)
63
+ - [Requirements](#requirements)
64
+ - [Development](#development)
65
+ - [Testing](#testing)
66
+ - [Contributing](#contributing)
67
+ - [License](#license)
68
+
69
+ ## Features
70
+
71
+ ### 🚀 High-Performance Bulk Operations
72
+
73
+ - **Multithreaded Processing**:
74
+ Parallel execution of database operations for maximum speed
75
+ - **Automatic Chunking**:
76
+ Configurable batch sizes (default: 1000) for memory-efficient processing
77
+ - **Transaction Safety**:
78
+ Atomic operations with intelligent transaction management
79
+ - **Dependency Resolution**:
80
+ Automatic topological sorting for foreign key relationships
81
+
82
+ ### 🛠️ Database Utilities
83
+
84
+ - **Bulk Create/Update/Delete**: Process thousands of records efficiently
85
+ - **Deletion Simulation**:
86
+ Preview cascade effects before executing destructive operations
87
+ - **Bulk Comparison**:
88
+ Detect differences between datasets with field-level hashing
89
+ - **Raw SQL Execution**: Safe parameter binding with automatic cursor management
90
+
91
+ ### 📦 Model Utilities
92
+
93
+ - **BaseModel**:
94
+ Abstract base with `created_at`, `updated_at`, and type-safe `meta` property
95
+ - **Topological Sorting**: Automatic dependency ordering for model operations
96
+ - **Field Introspection**: Type-safe utilities for working with model fields
97
+
98
+ ### 🎯 Management Command Framework
99
+
100
+ - **ABCBaseCommand**: Template method pattern with automatic logging
101
+ - **ImportDataBaseCommand**: Structured data import with Polars integration
102
+ - **Built-in Arguments**:
103
+ Standard options for dry-run, batch size, threading, and more
104
+ - **Type Safety**: Full type hints with abstract method enforcement
105
+
106
+ ## Installation
107
+
108
+ ```bash
109
+ pip install winidjango
110
+ ```
111
+
112
+ Or using `uv`:
113
+
114
+ ```bash
115
+ uv add winidjango
116
+ ```
117
+
118
+ ## Quick Start
119
+
120
+ ### Bulk Operations
121
+
122
+ ```python
123
+ from winidjango.src.db.bulk import bulk_create_in_steps
124
+
125
+ # Create 10,000 records in batches of 1000
126
+ authors = [Author(name=f"Author {i}") for i in range(10000)]
127
+ created = bulk_create_in_steps(Author, authors, step=1000)
128
+ ```
129
+
130
+ ### Automatic Dependency Resolution
131
+
132
+ ```python
133
+ from winidjango.src.db.bulk import bulk_create_bulks_in_steps
134
+
135
+ # Create related models in correct order automatically
136
+ results = bulk_create_bulks_in_steps({
137
+ Author: authors,
138
+ Book: books, # Created after Author
139
+ Review: reviews, # Created after Book
140
+ })
141
+ ```
142
+
143
+ ### Deletion Simulation
144
+
145
+ ```python
146
+ from winidjango.src.db.bulk import simulate_bulk_deletion
147
+
148
+ # Preview what would be deleted
149
+ deletion_preview = simulate_bulk_deletion(Author, authors_to_delete)
150
+ print(f"Would delete {len(deletion_preview[Author])} authors")
151
+ print(f"Would cascade delete {len(deletion_preview[Book])} books")
152
+ ```
153
+
154
+ ### Custom Management Command
155
+
156
+ ```python
157
+ from winidjango.src.commands.base.base import ABCBaseCommand
158
+ from argparse import ArgumentParser
159
+
160
+ class MyCommand(ABCBaseCommand):
161
+ def add_command_arguments(self, parser: ArgumentParser) -> None:
162
+ parser.add_argument('--input-file', type=str, required=True)
163
+
164
+ def handle_command(self) -> None:
165
+ input_file = self.get_option('input_file')
166
+ dry_run = self.get_option('dry_run') # Built-in
167
+
168
+ if dry_run:
169
+ self.stdout.write('Dry run mode')
170
+
171
+ # Your logic here
172
+ ```
173
+
174
+ ### Data Import Command
175
+
176
+ ```python
177
+ from winidjango.src.commands.import_data import ImportDataBaseCommand
178
+ import polars as pl
179
+
180
+ class ImportUsersCommand(ImportDataBaseCommand):
181
+ def handle_import(self) -> pl.DataFrame:
182
+ return pl.read_csv("users.csv")
183
+
184
+ def get_cleaning_df_cls(self) -> type[CleaningDF]:
185
+ return MyCleaningDF
186
+
187
+ def get_bulks_by_model(
188
+ self, df: pl.DataFrame
189
+ ) -> dict[type[Model], Iterable[Model]]:
190
+ users = [User(name=row["name"]) for row in df.iter_rows(named=True)]
191
+ return {User: users}
192
+ ```
193
+
194
+ ## Documentation
195
+
196
+ Comprehensive documentation is available in the [`docs/`](docs/) directory:
197
+
198
+ - **[Database Utilities](docs/db.md)**
199
+ - Bulk operations, model utilities, and SQL helpers
200
+ - **[Management Commands](docs/commands.md)** - Command framework and data import patterns
201
+ - **[API Reference](docs/index.md)** - Complete API documentation
202
+
203
+ ## Requirements
204
+
205
+ - **Python**: 3.12+
206
+ - **Django**: Compatible with modern Django versions
207
+ - **Dependencies**:
208
+ - `django`
209
+ - `django-stubs-ext`
210
+ - `winiutils`
211
+
212
+ ## Development
213
+
214
+ ### Setup
215
+
216
+ ```bash
217
+ # Clone the repository
218
+ git clone https://github.com/Winipedia/winidjango.git
219
+ cd winidjango
220
+
221
+ # Install dependencies
222
+ uv sync
223
+
224
+ # Install pre-commit hooks
225
+ pre-commit install
226
+ ```
227
+
228
+ ### Code Quality
229
+
230
+ This project uses:
231
+
232
+ - **mypy**: Strict type checking
233
+ - **ruff**: Linting and formatting
234
+ - **bandit**: Security analysis
235
+ - **pytest**: Testing framework
236
+
237
+ ```bash
238
+ # Run type checking
239
+ mypy .
240
+
241
+ # Run linting
242
+ ruff check .
243
+
244
+ # Run security checks
245
+ bandit -r winidjango
246
+
247
+ # Format code
248
+ ruff format .
249
+ ```
250
+
251
+ ## Testing
252
+
253
+ ```bash
254
+ # Run all tests
255
+ pytest
256
+
257
+ # Run with coverage
258
+ pytest --cov=winidjango
259
+
260
+ # Run specific test file
261
+ pytest tests/test_winidjango/test_src/test_db/test_bulk.py
262
+ ```
263
+
264
+ ## Contributing
265
+
266
+ Contributions are welcome! Please feel free to submit a Pull Request.
267
+ For major changes, please open an issue first
268
+ to discuss what you would like to change.
269
+
270
+ 1. Fork the repository
271
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
272
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
273
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
274
+ 5. Open a Pull Request
275
+
276
+ ## License
277
+
278
+ This project is licensed under the MIT License,
279
+ see the [LICENSE](LICENSE) file for details.
280
+
281
+ ## Acknowledgments
282
+
283
+ - Built with [pyrig](https://github.com/Winipedia/pyrig) - Python project scaffolding tool
284
+ - Integrates with [winiutils](https://github.com/Winipedia/winiutils) - General Python utilities
285
+
286
+ ---
@@ -0,0 +1,29 @@
1
+ winidjango/__init__.py,sha256=cbh5Y743eDQHW25CF2WyW6EM_CGEK5jU9bBBwtoqbVA,973
2
+ winidjango/dev/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
3
+ winidjango/dev/builders/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
4
+ winidjango/dev/cli/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
5
+ winidjango/dev/cli/shared_subcommands.py,sha256=3sJl1DD-DVRok2BXFWjncF_NLnMZL_NLqMWozmQm390,365
6
+ winidjango/dev/cli/subcommands.py,sha256=iurWZwJwEKAfGpfjkn1YOhnRbIruCB4ouE-8R_Lh3JY,228
7
+ winidjango/dev/configs/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
8
+ winidjango/dev/configs/configs.py,sha256=N85kVaRHCHXc-ny0jbuYQwTcrxQJx_X_BcG30IcyrGw,586
9
+ winidjango/dev/management/__init__.py,sha256=dZm5jWpCTZl7I44Ykr6S1_yU4ATNG5evtUL3DmGjCLs,193
10
+ winidjango/dev/tests/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
11
+ winidjango/dev/tests/fixtures/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
12
+ winidjango/main.py,sha256=TdxQpUtNZIiYCsEdysMlQW-1UNy0__WTax-Ot5E2eYQ,144
13
+ winidjango/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ winidjango/resources/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
15
+ winidjango/src/__init__.py,sha256=LOIpCo9zHZCs_ifeiw3PLILm6ik1hyIHJNC9gYir1Eg,569
16
+ winidjango/src/commands/__init__.py,sha256=5rI9IBYLsGpwbdxPzEP21EWlRFC0-6EzTm3NRc758Fk,376
17
+ winidjango/src/commands/base/__init__.py,sha256=ZpOXolcoL9RnhppgzKqF0581gcLcsjin8hydYUd_QZc,320
18
+ winidjango/src/commands/base/base.py,sha256=SeJxHiH_YA2J0bEhGGScniXcwekFLUB8MmdCTJ5hGik,6162
19
+ winidjango/src/commands/import_data.py,sha256=kQatdfQ7HRH2VFSwmVGx6s4J7AAh2FKatTEqWLQDScI,4354
20
+ winidjango/src/db/__init__.py,sha256=x1zuU0bJMuPxqhBAW6g7Ob8BrcC0wyW_NlLn9S4ZaqI,323
21
+ winidjango/src/db/bulk.py,sha256=5mJyGyt3vtu8cf-xji3dd2zxAU5eH0tuV85RjXIMriw,20110
22
+ winidjango/src/db/fields.py,sha256=sa_qe36rQoh7zzsgbadsREKYOqzgx0WDar5tPdZ4rpE,2098
23
+ winidjango/src/db/models.py,sha256=LBdN1pbG4jp_yy_pEKdXxNd05ZXAVCkj7srvO-UE1vw,5750
24
+ winidjango/src/db/sql.py,sha256=MMiJyc8uhJ_4VcN2s6i8koNfvDKxsSv3Gpb_kD-01IE,1638
25
+ winidjango-2.0.55.dist-info/licenses/LICENSE,sha256=o316mE2gGzd__JT69p7S_zlOmKiHh8YjpImCCcWyTvM,1066
26
+ winidjango-2.0.55.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
27
+ winidjango-2.0.55.dist-info/entry_points.txt,sha256=qM1ENsRWSLulhF8Cy92TeHW-4v8SAgzkRW7hNHXHG-4,55
28
+ winidjango-2.0.55.dist-info/METADATA,sha256=yGPABM2d9y90rVLunk5I0rgS2HRRlgI1N688CODA9Ak,9209
29
+ winidjango-2.0.55.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.21
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ winidjango = pyrig.dev.cli.cli:main
3
+
@@ -1,4 +0,0 @@
1
- """Build script.
2
-
3
- All subclasses of Builder in the builds package are automatically called.
4
- """