Compare commits
15 Commits
6aa6c9e4dd
...
main
| Author | SHA256 | Date | |
|---|---|---|---|
| df9d786b70 | |||
| 0c5686b3a7 | |||
| b04a997561 | |||
| 13793f2d70 | |||
| 7f749380ff | |||
| 2464a57a42 | |||
| c58cccd31c | |||
| 5899992240 | |||
| 894dc2f3e0 | |||
| 4216e833e5 | |||
| b547d69d48 | |||
| 536c35e69c | |||
| 0ef6530f92 | |||
| 377e481803 | |||
| 38b6807e70 |
5
.gitignore
vendored
Normal file → Executable file
5
.gitignore
vendored
Normal file → Executable file
@@ -6,5 +6,6 @@ dist/
|
|||||||
wheels/
|
wheels/
|
||||||
*.egg-info
|
*.egg-info
|
||||||
|
|
||||||
# Virtual environments
|
# Logs
|
||||||
.venv
|
logs/
|
||||||
|
typings/
|
||||||
1
.idea/.gitignore
generated
vendored
Normal file
1
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
workspace.xml
|
||||||
11
.idea/compose_gen.iml
generated
Normal file
11
.idea/compose_gen.iml
generated
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="uv (compose_gen)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
13
.idea/dictionaries/project.xml
generated
Normal file
13
.idea/dictionaries/project.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<component name="ProjectDictionaryState">
|
||||||
|
<dictionary name="project">
|
||||||
|
<words>
|
||||||
|
<w>ccamper</w>
|
||||||
|
<w>certresolver</w>
|
||||||
|
<w>exts</w>
|
||||||
|
<w>pswd</w>
|
||||||
|
<w>stryten</w>
|
||||||
|
<w>traefik</w>
|
||||||
|
<w>websecure</w>
|
||||||
|
</words>
|
||||||
|
</dictionary>
|
||||||
|
</component>
|
||||||
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="PROJECT_PROFILE" value="Default" />
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
13
.idea/misc.xml
generated
Normal file
13
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="uv (compose_gen)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="uv (compose_gen)" project-jdk-type="Python SDK" />
|
||||||
|
<component name="PyrightConfiguration">
|
||||||
|
<option name="enabled" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="RuffConfiguration">
|
||||||
|
<option name="enabled" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/compose_gen.iml" filepath="$PROJECT_DIR$/.idea/compose_gen.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
192
.idea/workspace.xml
generated
Normal file
192
.idea/workspace.xml
generated
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="0e000b98-45cd-46e2-a251-61e1b2cb3449" name="Changes" comment="sync">
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/compose/compose.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/compose/service/__init__.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/compose/service/env.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/compose/service/health_check.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/compose/service/networks.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/compose/service/port.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/compose/service/service.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/compose/service/volumes.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/compose/volume_files.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/env/__init__.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/env/env_data.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/env/env_row.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/paths/__init__.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/paths/dest.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/paths/org.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/paths/src.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/render/__init__.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/render/bind_vols.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/docker_compose/domain/render/render.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/inspectionProfiles/profiles_settings.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/profiles_settings.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/pyproject.toml" beforeDir="false" afterPath="$PROJECT_DIR$/pyproject.toml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/docker_compose/__init__.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/__main__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/docker_compose/__main__.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/compose_data/__init__.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/compose_data/compose_yaml.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/compose_data/data.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/compose_data/dest_paths.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/compose_data/main.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/compose_data/net.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/compose_data/net_yaml.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/compose_data/service.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/compose_data/services_yaml.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/compose_data/src_paths.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/compose_data/volume_yaml.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/env/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/docker_compose/application/__init__.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/env/data.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/env/main.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/org/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/docker_compose/domain/__init__.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/org/data.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/org/org_yaml.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/render/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/docker_compose/domain/compose/__init__.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/render/main.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/util/Ts.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/util/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/docker_compose/util/__init__.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/util/replace.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/docker_compose/util/yaml_util.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/uv.lock" beforeDir="false" afterPath="$PROJECT_DIR$/uv.lock" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="Python Script" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="FormatOnSaveOptions">
|
||||||
|
<option name="myRunOnSave" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="OptimizeOnSaveOptions">
|
||||||
|
<option name="myRunOnSave" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="ProblemsViewState">
|
||||||
|
<option name="selectedTabId" value="CurrentFile" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"customColor": "",
|
||||||
|
"associatedIndex": 1
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="38E5WemR1iIcI5oJ4g3eGxiweao" />
|
||||||
|
<component name="ProjectLevelVcsManager">
|
||||||
|
<ConfirmationsSetting value="2" id="Add" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">{
|
||||||
|
"keyToString": {
|
||||||
|
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||||
|
"Python.__init__.executor": "Run",
|
||||||
|
"Python.__main__.executor": "Run",
|
||||||
|
"Python.compose.executor": "Run",
|
||||||
|
"Python.dest_paths.executor": "Run",
|
||||||
|
"Python.env.executor": "Run",
|
||||||
|
"Python.env_data.executor": "Run",
|
||||||
|
"Python.env_row.executor": "Run",
|
||||||
|
"Python.models.executor": "Run",
|
||||||
|
"Python.networks.executor": "Run",
|
||||||
|
"Python.org.executor": "Run",
|
||||||
|
"Python.service.executor": "Run",
|
||||||
|
"Python.service_objs.executor": "Run",
|
||||||
|
"Python.src_paths.executor": "Run",
|
||||||
|
"Python.test.executor": "Run",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
|
"git-widget-placeholder": "main",
|
||||||
|
"run.code.analysis.last.selected.profile": "aDefault",
|
||||||
|
"settings.editor.selected.configurable": "preferences.pluginManager"
|
||||||
|
}
|
||||||
|
}</component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$/src/docker_compose/domain" />
|
||||||
|
<recent name="$PROJECT_DIR$/src/docker_compose/domain/compse" />
|
||||||
|
<recent name="$PROJECT_DIR$/src/docker_compose/domain/paths" />
|
||||||
|
<recent name="$PROJECT_DIR$/src/docker_compose/application" />
|
||||||
|
<recent name="$PROJECT_DIR$/src/docker_compose/domain/org" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="SharedIndexes">
|
||||||
|
<attachedChunks>
|
||||||
|
<set>
|
||||||
|
<option value="bundled-python-sdk-ca5e2b39c7df-6e1f45a539f7-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-253.29346.308" />
|
||||||
|
</set>
|
||||||
|
</attachedChunks>
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="0e000b98-45cd-46e2-a251-61e1b2cb3449" name="Changes" comment="" />
|
||||||
|
<created>1768351925652</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1768351925652</updated>
|
||||||
|
<workItem from="1768351927641" duration="7710000" />
|
||||||
|
<workItem from="1768368636277" duration="609000" />
|
||||||
|
<workItem from="1768440206331" duration="691000" />
|
||||||
|
<workItem from="1768490085988" duration="1768000" />
|
||||||
|
<workItem from="1768491864020" duration="5557000" />
|
||||||
|
<workItem from="1768599854689" duration="10777000" />
|
||||||
|
<workItem from="1768703591117" duration="4164000" />
|
||||||
|
<workItem from="1768841225994" duration="42537000" />
|
||||||
|
<workItem from="1768935433333" duration="26709000" />
|
||||||
|
<workItem from="1769012060714" duration="18853000" />
|
||||||
|
<workItem from="1769045301500" duration="3237000" />
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00001" summary="sync">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1768356921807</created>
|
||||||
|
<option name="number" value="00001" />
|
||||||
|
<option name="presentableId" value="LOCAL-00001" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1768356921807</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="2" />
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="Vcs.Log.Tabs.Properties">
|
||||||
|
<option name="TAB_STATES">
|
||||||
|
<map>
|
||||||
|
<entry key="MAIN">
|
||||||
|
<value>
|
||||||
|
<State />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="VcsManagerConfiguration">
|
||||||
|
<MESSAGE value="sync" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="sync" />
|
||||||
|
</component>
|
||||||
|
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
||||||
|
<SUITE FILE_PATH="coverage/compose_gen$networks.coverage" NAME="networks Coverage Results" MODIFIED="1768958487349" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/docker_compose/service" />
|
||||||
|
<SUITE FILE_PATH="coverage/compose_gen$env_data.coverage" NAME="env_data Coverage Results" MODIFIED="1768880278530" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/docker_compose/env" />
|
||||||
|
<SUITE FILE_PATH="coverage/compose_gen$service_objs.coverage" NAME="service_objs Coverage Results" MODIFIED="1768623924934" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/docker_compose" />
|
||||||
|
<SUITE FILE_PATH="coverage/compose_gen$__main__.coverage" NAME="__main__ Coverage Results" MODIFIED="1768357020724" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/docker_compose" />
|
||||||
|
<SUITE FILE_PATH="coverage/compose_gen$dest_paths.coverage" NAME="dest_paths Coverage Results" MODIFIED="1768870284589" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/docker_compose/compose_data" />
|
||||||
|
<SUITE FILE_PATH="coverage/compose_gen$env_row.coverage" NAME="env_row Coverage Results" MODIFIED="1768879726606" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/docker_compose/env" />
|
||||||
|
<SUITE FILE_PATH="coverage/compose_gen$__init__.coverage" NAME="__init__ Coverage Results" MODIFIED="1768880253063" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/docker_compose" />
|
||||||
|
<SUITE FILE_PATH="coverage/compose_gen$test.coverage" NAME="test Coverage Results" MODIFIED="1768883881176" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/docker_compose" />
|
||||||
|
<SUITE FILE_PATH="coverage/compose_gen$service.coverage" NAME="service Coverage Results" MODIFIED="1768976241918" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/docker_compose/domain/service" />
|
||||||
|
<SUITE FILE_PATH="coverage/compose_gen$src_paths.coverage" NAME="src_paths Coverage Results" MODIFIED="1768870848389" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/docker_compose/compose_data" />
|
||||||
|
<SUITE FILE_PATH="coverage/compose_gen$compose.coverage" NAME="compose Coverage Results" MODIFIED="1768627784986" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/docker_compose" />
|
||||||
|
<SUITE FILE_PATH="coverage/compose_gen$org.coverage" NAME="org Coverage Results" MODIFIED="1768959029442" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/docker_compose" />
|
||||||
|
<SUITE FILE_PATH="coverage/compose_gen$env.coverage" NAME="env Coverage Results" MODIFIED="1768976090883" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/docker_compose/domain/service" />
|
||||||
|
<SUITE FILE_PATH="coverage/compose_gen$models.coverage" NAME="models Coverage Results" MODIFIED="1768879410018" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/docker_compose/env" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
0
.python-version
Normal file → Executable file
0
.python-version
Normal file → Executable file
4
.vscode/settings.json
vendored
Normal file → Executable file
4
.vscode/settings.json
vendored
Normal file → Executable file
@@ -9,7 +9,11 @@
|
|||||||
"editor.defaultFormatter": "charliermarsh.ruff"
|
"editor.defaultFormatter": "charliermarsh.ruff"
|
||||||
},
|
},
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"autoslot",
|
||||||
|
"ccamper",
|
||||||
"certresolver",
|
"certresolver",
|
||||||
|
"funcs",
|
||||||
|
"STRYTEN",
|
||||||
"traefik",
|
"traefik",
|
||||||
"websecure"
|
"websecure"
|
||||||
]
|
]
|
||||||
|
|||||||
18
docs/cls.md
Normal file
18
docs/cls.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
CfgData {
|
||||||
|
SrcPaths src_paths
|
||||||
|
OrgData org_data
|
||||||
|
ComposePaths compose_paths
|
||||||
|
DestPaths dest_paths
|
||||||
|
}
|
||||||
|
OrgData {
|
||||||
|
org_app ReplaceUniqe
|
||||||
|
App app
|
||||||
|
Org org
|
||||||
|
Url url
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CfgData ||--|| OrgData : contains
|
||||||
|
```
|
||||||
0
docs/workflow.md
Normal file → Executable file
0
docs/workflow.md
Normal file → Executable file
24
pyproject.toml
Normal file → Executable file
24
pyproject.toml
Normal file → Executable file
@@ -1,19 +1,35 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "compose"
|
name = "docker_compose"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Add your description here"
|
description = "Add your description here"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [{ name = "Christian Camper", email = "ccamper7@gmail.com" }]
|
authors = [{ name = "Christian Camper", email = "ccamper7@gmail.com" }]
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"basedpyright>=1.36.1",
|
"autoslot>=2025.11.1",
|
||||||
|
"basedpyright>=1.37.1",
|
||||||
|
"loguru>=0.7.3",
|
||||||
|
"pydantic>=2.12.5",
|
||||||
"pyyaml>=6.0.3",
|
"pyyaml>=6.0.3",
|
||||||
"ruff>=0.14.9",
|
"ruff==0.14.13",
|
||||||
|
"sqlalchemy>=2.0.45",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
compose = "compose:main"
|
docker_compose = "docker_compose:main"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["uv_build>=0.9.17,<0.10.0"]
|
requires = ["uv_build>=0.9.17,<0.10.0"]
|
||||||
build-backend = "uv_build"
|
build-backend = "uv_build"
|
||||||
|
|
||||||
|
[tool.basedpyright]
|
||||||
|
reportExplicitAny = "none"
|
||||||
|
reportImportCycles = "none"
|
||||||
|
executionEnvironments = [
|
||||||
|
{ root = "src" }
|
||||||
|
]
|
||||||
|
exclude = [
|
||||||
|
".*",
|
||||||
|
"typings",
|
||||||
|
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
from collections.abc import Iterable, Iterator
|
|
||||||
|
|
||||||
from compose.cfg import CFG_ROOT, TRAEFIK_PATH
|
|
||||||
from compose.cfg.factory import cfg_data_factory
|
|
||||||
from compose.compose.factory import compose_factory
|
|
||||||
from compose.rendered.entity import Rendered
|
|
||||||
from compose.rendered.factory import rendered_factory
|
|
||||||
from compose.rendered.util import write
|
|
||||||
from compose.src_path.entity import src_paths_factory
|
|
||||||
from compose.template.factory import template_factory
|
|
||||||
|
|
||||||
|
|
||||||
def load_all() -> Iterable[Rendered]:
|
|
||||||
for dir in CFG_ROOT.iterdir():
|
|
||||||
paths = src_paths_factory(dir)
|
|
||||||
cfg = cfg_data_factory(paths)
|
|
||||||
parsed = compose_factory(cfg)
|
|
||||||
for template in template_factory(parsed):
|
|
||||||
yield rendered_factory(template)
|
|
||||||
|
|
||||||
|
|
||||||
def render_all() -> Iterator[Rendered]:
|
|
||||||
for rendered in load_all():
|
|
||||||
write(rendered)
|
|
||||||
yield rendered
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
renders = render_all()
|
|
||||||
src_paths = src_paths_factory(TRAEFIK_PATH)
|
|
||||||
cfg_data = cfg_data_factory(src_paths)
|
|
||||||
traefik = compose_factory(cfg_data)
|
|
||||||
for template in template_factory(traefik):
|
|
||||||
rendered = rendered_factory(template)
|
|
||||||
write(rendered)
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
from collections.abc import Mapping
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
type nested_list = list[str | nested_list]
|
|
||||||
type T_Primitive = bool | int | str
|
|
||||||
type T_PrimVal = T_Primitive | list[T_Primitive] | T_PrimDict
|
|
||||||
type T_PrimDict = Mapping[T_Primitive, T_PrimVal]
|
|
||||||
type T_YamlVals = T_Primitive | list[T_Primitive | T_YamlDict] | T_YamlDict
|
|
||||||
type T_YamlDict = Mapping[str, T_YamlVals]
|
|
||||||
|
|
||||||
CFG_ROOT = Path("/data/cfg")
|
|
||||||
DATA_ROOT = Path("/data")
|
|
||||||
TRAEFIK_PATH = Path("/data/traefik")
|
|
||||||
|
|
||||||
# TCo_YamlVals = TypeVar(
|
|
||||||
# "TCo_YamlVals",
|
|
||||||
# bound=T_Primitive | list[T_Primitive | T_YamlDict] | T_YamlDict,
|
|
||||||
# covariant=True,
|
|
||||||
# )
|
|
||||||
# type TCo_YamlDict = dict[str, TCo_YamlVals]
|
|
||||||
|
|
||||||
# TCo_YamlDict = TypeVar("TCo_YamlDict", bound=dict[str, T_YamlVals], covariant=True)
|
|
||||||
|
|
||||||
|
|
||||||
# class HasServices(TypedDict):
|
|
||||||
# services: dict[str, ComposeService]
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import NotRequired, TypedDict, final
|
|
||||||
|
|
||||||
from compose.src_path.entity import SrcPaths
|
|
||||||
|
|
||||||
|
|
||||||
class OrgDataYaml(TypedDict):
|
|
||||||
org: str
|
|
||||||
url: NotRequired[str]
|
|
||||||
|
|
||||||
|
|
||||||
class CfgDataYaml(TypedDict):
|
|
||||||
services: list[str]
|
|
||||||
volumes: NotRequired[list[str]]
|
|
||||||
orgs: list[OrgDataYaml]
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class OrgData:
|
|
||||||
org: str
|
|
||||||
url: str | None
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class CfgData:
|
|
||||||
src_paths: SrcPaths
|
|
||||||
name: str
|
|
||||||
services: frozenset[Path]
|
|
||||||
volumes: frozenset[Path] | None
|
|
||||||
orgs: frozenset[OrgData]
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
from typing import cast
|
|
||||||
|
|
||||||
from compose.cfg.entity import CfgData, CfgDataYaml, OrgDataYaml
|
|
||||||
from compose.cfg.get import cfg_get_orgs
|
|
||||||
from compose.src_path.entity import SrcPaths
|
|
||||||
from compose.src_path.get import src_path_get_services, src_path_get_volumes
|
|
||||||
from compose.util import read_yml, validate_typed_dict
|
|
||||||
|
|
||||||
|
|
||||||
def cfg_data_yml_factory(file: Path) -> CfgDataYaml:
|
|
||||||
data = cast(CfgDataYaml, read_yml(file))
|
|
||||||
validate_typed_dict(CfgDataYaml, data, file)
|
|
||||||
|
|
||||||
orgs_key = "orgs"
|
|
||||||
for org in data[orgs_key]:
|
|
||||||
validate_typed_dict(OrgDataYaml, org, file, (orgs_key,))
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def cfg_data_factory(src_paths: SrcPaths) -> CfgData:
|
|
||||||
data = cfg_data_yml_factory(src_paths.cfg_file)
|
|
||||||
vols = frozenset(src_path_get_volumes(src_paths, data))
|
|
||||||
return CfgData(
|
|
||||||
src_paths,
|
|
||||||
src_paths.cfg_dir.name,
|
|
||||||
frozenset(src_path_get_services(src_paths, data)),
|
|
||||||
vols if vols else None,
|
|
||||||
frozenset(cfg_get_orgs(data)),
|
|
||||||
)
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
from collections.abc import Iterator
|
|
||||||
from typing import cast
|
|
||||||
|
|
||||||
from compose.cfg.entity import CfgData, CfgDataYaml, OrgData
|
|
||||||
from compose.compose.entity import VolYaml
|
|
||||||
from compose.service.entity import Service, T_Compose
|
|
||||||
from compose.service.factory import services_yaml_factory
|
|
||||||
from compose.util import get_replace_name, read_yml
|
|
||||||
|
|
||||||
|
|
||||||
def cfg_get_orgs(data: CfgDataYaml) -> Iterator[OrgData]:
|
|
||||||
for org_data in data["orgs"]:
|
|
||||||
yield OrgData(
|
|
||||||
org_data["org"],
|
|
||||||
org_data.get("url"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_sec_opts(
|
|
||||||
data: T_Compose,
|
|
||||||
) -> frozenset[str]:
|
|
||||||
sec_opts = frozenset(
|
|
||||||
"no-new-privileges:true",
|
|
||||||
)
|
|
||||||
sec = data.get("security_opt")
|
|
||||||
if not sec:
|
|
||||||
return sec_opts
|
|
||||||
return sec_opts.union(sec)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_labels(
|
|
||||||
data: T_Compose,
|
|
||||||
) -> frozenset[str] | None:
|
|
||||||
org_name = get_replace_name("org_name")
|
|
||||||
url = get_replace_name("url")
|
|
||||||
traefik_labels = frozenset(
|
|
||||||
(
|
|
||||||
f"traefik.http.routers.{org_name}.rule=Host(`{url}`)",
|
|
||||||
f"traefik.http.routers.{org_name}.entrypoints=websecure",
|
|
||||||
f"traefik.docker.network={org_name}_proxy",
|
|
||||||
f"traefik.http.routers.{org_name}.tls.certresolver=le",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
labels = data.get("labels")
|
|
||||||
if not labels:
|
|
||||||
return
|
|
||||||
if "traefik.enable=true" not in labels:
|
|
||||||
return frozenset(labels)
|
|
||||||
return traefik_labels.union(labels)
|
|
||||||
|
|
||||||
|
|
||||||
def cfg_get_services(cfg_data: CfgData) -> Iterator[tuple[str, Service]]:
|
|
||||||
for path in cfg_data.services:
|
|
||||||
data = services_yaml_factory(path)
|
|
||||||
# yield path.stem, Service.from_dict(data)
|
|
||||||
# @classmethod
|
|
||||||
command = data.get("command")
|
|
||||||
volumes = data.get("volumes")
|
|
||||||
entry = data.get("entrypoint")
|
|
||||||
|
|
||||||
service = Service(
|
|
||||||
tuple(command) if command else None,
|
|
||||||
get_replace_name("org_name"),
|
|
||||||
tuple(entry) if entry else None,
|
|
||||||
data.get("environment"),
|
|
||||||
data["image"],
|
|
||||||
_get_labels(data),
|
|
||||||
None,
|
|
||||||
Service.get_nets(data),
|
|
||||||
"unless-stopped",
|
|
||||||
_get_sec_opts(data),
|
|
||||||
data.get("user"),
|
|
||||||
frozenset(volumes) if volumes else None,
|
|
||||||
)
|
|
||||||
yield path.stem, service
|
|
||||||
|
|
||||||
|
|
||||||
def cfg_get_volumes(cfg_data: CfgData) -> Iterator[tuple[str, VolYaml]]:
|
|
||||||
vols = cfg_data.volumes
|
|
||||||
if vols is None:
|
|
||||||
return
|
|
||||||
for path in vols:
|
|
||||||
yield path.stem, cast(VolYaml, read_yml(path))
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
from dataclasses import asdict, dataclass
|
|
||||||
from typing import Literal, NotRequired, TypedDict, final
|
|
||||||
|
|
||||||
from compose.cfg import T_YamlDict
|
|
||||||
from compose.cfg.entity import CfgData
|
|
||||||
from compose.net.entities import Net, NetTraefik, NetYaml
|
|
||||||
from compose.service.entity import Service, ServiceYaml, TraefikService
|
|
||||||
from compose.util import to_yaml
|
|
||||||
|
|
||||||
type VolYaml = dict[str, T_YamlDict]
|
|
||||||
|
|
||||||
|
|
||||||
class ComposeYaml(TypedDict):
|
|
||||||
name: str
|
|
||||||
services: dict[str, ServiceYaml]
|
|
||||||
networks: NotRequired[NetYaml]
|
|
||||||
volumes: NotRequired[dict[str, T_YamlDict]]
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class Compose:
|
|
||||||
cfg: CfgData
|
|
||||||
services: dict[str, Service]
|
|
||||||
networks: Net | None
|
|
||||||
volumes: VolYaml | None
|
|
||||||
|
|
||||||
# @classmethod
|
|
||||||
# def from_dict(cls, cfg: CfgData, data: ComposeYaml) -> Self:
|
|
||||||
# # services = dict[str, ComposeService]()
|
|
||||||
# services = dict(_get_services_dict(data))
|
|
||||||
# # vols = frozenset(_get_volumes_dict(data))
|
|
||||||
# return cls(
|
|
||||||
# cfg,
|
|
||||||
# services,
|
|
||||||
# services_get_networks(services.values()),
|
|
||||||
# data.get("volumes"),
|
|
||||||
# )
|
|
||||||
|
|
||||||
def as_yaml(self) -> str:
|
|
||||||
return to_yaml(asdict(self))
|
|
||||||
|
|
||||||
|
|
||||||
# def _get_services_dict(data: ComposeYaml):
|
|
||||||
# for k, v in data["services"].items():
|
|
||||||
# yield k, Service.from_dict(v)
|
|
||||||
|
|
||||||
|
|
||||||
# def _get_volumes_dict(data: ComposeYaml) -> Iterator[VolYaml]:
|
|
||||||
# vols = data.get("volumes")
|
|
||||||
# if vols is None:
|
|
||||||
# return
|
|
||||||
# for k, v in vols.items():
|
|
||||||
# yield {k: v}
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class TraefikCompose:
|
|
||||||
cfg: CfgData
|
|
||||||
services: dict[Literal["traefik"], TraefikService]
|
|
||||||
networks: NetTraefik
|
|
||||||
volumes: None
|
|
||||||
|
|
||||||
def as_yaml(self) -> str:
|
|
||||||
return to_yaml(asdict(self))
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
from compose.cfg.entity import CfgData
|
|
||||||
from compose.cfg.get import cfg_get_services, cfg_get_volumes
|
|
||||||
from compose.compose.entity import Compose, VolYaml
|
|
||||||
|
|
||||||
# from compose.service.factory import get_traefik_service
|
|
||||||
from compose.service.get import services_get_networks
|
|
||||||
|
|
||||||
|
|
||||||
def compose_factory(cfg_data: CfgData) -> Compose:
|
|
||||||
services = dict(cfg_get_services(cfg_data))
|
|
||||||
vols: VolYaml | None = dict(cfg_get_volumes(cfg_data))
|
|
||||||
|
|
||||||
return Compose(
|
|
||||||
cfg_data,
|
|
||||||
services,
|
|
||||||
services_get_networks(services.values()),
|
|
||||||
vols if vols else None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# def traefik_compose_factory(renders: Iterable[Rendered]) -> TraefikCompose:
|
|
||||||
# src_paths = src_paths_factory(TRAEFIK_PATH)
|
|
||||||
# cfg = cfg_data_factory(src_paths)
|
|
||||||
# # cfg = CfgData(
|
|
||||||
# # src_paths,
|
|
||||||
# # 'traefik',
|
|
||||||
# # frozenset((TRAEFIK_PATH.joinpath('traefik'),)),
|
|
||||||
# # None,
|
|
||||||
# # )
|
|
||||||
# service = get_traefik_service()
|
|
||||||
# nets: NetTraefik = dict(rendered_get_nets(renders))
|
|
||||||
# service["networks"] = list(nets.keys())
|
|
||||||
|
|
||||||
# return TraefikCompose(
|
|
||||||
# cfg,
|
|
||||||
# nets,
|
|
||||||
# {"traefik": TraefikService.from_dict(service)},
|
|
||||||
# )
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
from collections.abc import Iterator
|
|
||||||
|
|
||||||
from compose.compose.entity import Compose, TraefikCompose
|
|
||||||
from compose.util import get_replace_name
|
|
||||||
|
|
||||||
|
|
||||||
def compose_get_volumes(compose: Compose | TraefikCompose) -> Iterator[str]:
|
|
||||||
if isinstance(compose, TraefikCompose):
|
|
||||||
return
|
|
||||||
f = get_replace_name("DATA")
|
|
||||||
for app_data in compose.services.values():
|
|
||||||
if app_data.volumes is None:
|
|
||||||
return
|
|
||||||
for vol in app_data.volumes:
|
|
||||||
if not vol.startswith(f):
|
|
||||||
continue
|
|
||||||
yield vol.split(":", 1)[0]
|
|
||||||
|
|
||||||
|
|
||||||
# def compose_get_volumes(compose: Compose | TraefikCompose):
|
|
||||||
|
|
||||||
# return _volumes_sub(compose)
|
|
||||||
# # vols = set(_volumes_sub(compose))
|
|
||||||
# # if not vols:
|
|
||||||
# # return None
|
|
||||||
# # return vols
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import final
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class DestPaths:
|
|
||||||
data_dir: Path
|
|
||||||
env_file: Path
|
|
||||||
compose_file: Path
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
from compose.cfg import DATA_ROOT
|
|
||||||
from compose.dest_path.entity import DestPaths
|
|
||||||
from compose.template.entity import Template
|
|
||||||
|
|
||||||
|
|
||||||
def dest_paths_factory(template: Template) -> DestPaths:
|
|
||||||
r_args = template.replace_args
|
|
||||||
path = (
|
|
||||||
DATA_ROOT.joinpath(template.compose.cfg.name)
|
|
||||||
if r_args is None
|
|
||||||
else r_args.data.val.path
|
|
||||||
)
|
|
||||||
return DestPaths(
|
|
||||||
path,
|
|
||||||
path.joinpath(".env"),
|
|
||||||
path.joinpath("docker-compose.yml"),
|
|
||||||
)
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
from typing import NotRequired, TypedDict, final
|
|
||||||
|
|
||||||
type NetTraefik = dict[str, NetArgs]
|
|
||||||
|
|
||||||
|
|
||||||
class NetArgsYaml(TypedDict):
|
|
||||||
name: str
|
|
||||||
external: NotRequired[bool]
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class NetArgs:
|
|
||||||
name: str
|
|
||||||
external: bool | None
|
|
||||||
|
|
||||||
# @classmethod
|
|
||||||
# def from_dict(cls, data: NetArgsYaml) -> Self:
|
|
||||||
# return cls(
|
|
||||||
# data["name"],
|
|
||||||
# data.get("external"),
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
class NetYaml(TypedDict):
|
|
||||||
internal: NotRequired[NetArgsYaml]
|
|
||||||
proxy: NotRequired[NetArgsYaml]
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class Net:
|
|
||||||
internal: NetArgs | None
|
|
||||||
proxy: NetArgs | None
|
|
||||||
|
|
||||||
# @classmethod
|
|
||||||
# def from_dict(cls, data: NetYaml) -> Self:
|
|
||||||
# internal = data.get("internal")
|
|
||||||
# if internal is not None:
|
|
||||||
# internal = NetArgs.from_dict(internal)
|
|
||||||
# proxy = data.get("proxy")
|
|
||||||
# if proxy is not None:
|
|
||||||
# proxy = NetArgs.from_dict(proxy)
|
|
||||||
# return cls(internal, proxy)
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
from compose.net.entities import Net, NetArgs
|
|
||||||
from compose.util import get_replace_name
|
|
||||||
|
|
||||||
|
|
||||||
def net_args_factory(name: str, external: bool | None = None) -> NetArgs:
|
|
||||||
return NetArgs(name, external if external else None)
|
|
||||||
|
|
||||||
|
|
||||||
def net_factory(name: str, _internal: bool, _proxy: bool) -> Net:
|
|
||||||
return Net(
|
|
||||||
net_args_factory(name, _internal) if _internal else None,
|
|
||||||
net_args_factory(f"{name}_proxy", _proxy) if _proxy else None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def net_factory_re(_internal: bool, _proxy: bool) -> Net:
|
|
||||||
return net_factory(
|
|
||||||
get_replace_name("name"),
|
|
||||||
_internal,
|
|
||||||
_proxy,
|
|
||||||
)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import final
|
|
||||||
|
|
||||||
from compose.dest_path.entity import DestPaths
|
|
||||||
from compose.src_path.entity import SrcPaths
|
|
||||||
from compose.template.entity import Template
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class Rendered:
|
|
||||||
template: Template
|
|
||||||
src_paths: SrcPaths
|
|
||||||
dest_paths: DestPaths
|
|
||||||
volumes: frozenset[Path] | None
|
|
||||||
proxy_net: str | None
|
|
||||||
data: str
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
from functools import reduce
|
|
||||||
|
|
||||||
from compose.dest_path.factory import dest_paths_factory
|
|
||||||
from compose.rendered.entity import Rendered
|
|
||||||
from compose.template.entity import Template
|
|
||||||
from compose.template.get import template_get_proxy, template_get_vols
|
|
||||||
from compose.template.util import replace
|
|
||||||
|
|
||||||
|
|
||||||
def rendered_factory(template: Template) -> Rendered:
|
|
||||||
yml = template.compose.as_yaml()
|
|
||||||
if template.replace_args is not None:
|
|
||||||
yml = reduce(
|
|
||||||
lambda s, f: replace(f, s),
|
|
||||||
template.replace_args,
|
|
||||||
yml,
|
|
||||||
)
|
|
||||||
vols = frozenset(template_get_vols(template))
|
|
||||||
return Rendered(
|
|
||||||
template,
|
|
||||||
template.compose.cfg.src_paths,
|
|
||||||
dest_paths_factory(template),
|
|
||||||
vols if vols else None,
|
|
||||||
template_get_proxy(template),
|
|
||||||
yml,
|
|
||||||
)
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
from collections.abc import Iterable, Iterator
|
|
||||||
|
|
||||||
from compose.net.entities import NetArgs
|
|
||||||
from compose.net.factory import net_args_factory
|
|
||||||
from compose.rendered.entity import Rendered
|
|
||||||
|
|
||||||
|
|
||||||
def rendered_get_nets(renders: Iterable[Rendered]) -> Iterator[tuple[str, NetArgs]]:
|
|
||||||
for render in renders:
|
|
||||||
net = render.proxy_net
|
|
||||||
if net is None:
|
|
||||||
continue
|
|
||||||
yield net, net_args_factory(f"{net}_proxy")
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
from shutil import copyfile
|
|
||||||
|
|
||||||
from compose.rendered.entity import Rendered
|
|
||||||
|
|
||||||
|
|
||||||
def _mk_dir(path: Path) -> None:
|
|
||||||
if path.exists():
|
|
||||||
return
|
|
||||||
path.mkdir(parents=True)
|
|
||||||
|
|
||||||
|
|
||||||
def _mk_compose_dir(rendered: Rendered) -> None:
|
|
||||||
_mk_dir(rendered.dest_paths.data_dir)
|
|
||||||
vols = rendered.volumes
|
|
||||||
if vols is None:
|
|
||||||
return
|
|
||||||
for vol in vols:
|
|
||||||
_mk_dir(vol)
|
|
||||||
|
|
||||||
|
|
||||||
def _mk_compose_env(rendered: Rendered) -> None:
|
|
||||||
src = rendered.src_paths.env_file
|
|
||||||
dest = rendered.dest_paths.env_file
|
|
||||||
if src.exists() and not dest.exists():
|
|
||||||
_ = copyfile(src, dest)
|
|
||||||
|
|
||||||
|
|
||||||
def write_raw(path: Path, data: str) -> None:
|
|
||||||
with path.open("wt") as f:
|
|
||||||
_ = f.write(data)
|
|
||||||
|
|
||||||
|
|
||||||
def write(rendered: Rendered) -> None:
|
|
||||||
funcs = (_mk_compose_dir, _mk_compose_env)
|
|
||||||
for func in funcs:
|
|
||||||
func(rendered)
|
|
||||||
write_raw(rendered.dest_paths.compose_file, rendered.data)
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
from abc import ABCMeta, abstractmethod
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Literal, NotRequired, TypedDict, TypeVar, overload, override
|
|
||||||
|
|
||||||
from compose.cfg import T_Primitive
|
|
||||||
|
|
||||||
type T_NetAbc = str | Literal["proxy", "internal"]
|
|
||||||
TCo_NetABC = TypeVar("TCo_NetABC", bound=T_NetAbc, covariant=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceYamlAbc[T_net: T_NetAbc](TypedDict):
|
|
||||||
command: NotRequired[list[str]]
|
|
||||||
container_name: NotRequired[str]
|
|
||||||
entrypoint: NotRequired[list[str]]
|
|
||||||
environment: NotRequired[dict[str, T_Primitive]]
|
|
||||||
image: str
|
|
||||||
labels: NotRequired[list[str]]
|
|
||||||
logging: NotRequired[dict[str, str]]
|
|
||||||
networks: NotRequired[list[T_net]]
|
|
||||||
restart: NotRequired[str]
|
|
||||||
security_opt: NotRequired[list[str]]
|
|
||||||
user: NotRequired[str]
|
|
||||||
volumes: NotRequired[list[str]]
|
|
||||||
|
|
||||||
|
|
||||||
# TCo_ServiceYaml = TypeVar(
|
|
||||||
# "TCo_ServiceYaml",
|
|
||||||
# bound=ServiceYamlAbc[T_NetAbc],
|
|
||||||
# covariant=True,
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceYaml(ServiceYamlAbc[Literal["proxy", "internal"]]):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class TraefikServiceYaml(ServiceYamlAbc[str]):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
type T_Compose = ServiceYaml | TraefikServiceYaml
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class ServiceAbc[T_net: T_NetAbc, T_Yaml: T_Compose](metaclass=ABCMeta):
|
|
||||||
command: tuple[str, ...] | None
|
|
||||||
container_name: str
|
|
||||||
entrypoint: tuple[str, ...] | None
|
|
||||||
environment: dict[str, T_Primitive] | None
|
|
||||||
image: str
|
|
||||||
labels: frozenset[str] | None
|
|
||||||
logging: dict[str, str] | None
|
|
||||||
networks: frozenset[T_net] | None
|
|
||||||
restart: str
|
|
||||||
security_opt: frozenset[str]
|
|
||||||
user: str | None
|
|
||||||
volumes: frozenset[str] | None
|
|
||||||
|
|
||||||
# @classmethod
|
|
||||||
# def from_dict(cls, data: T_Yaml) -> Self:
|
|
||||||
# command = data.get("command")
|
|
||||||
# volumes = data.get("volumes")
|
|
||||||
# entry = data.get("entrypoint")
|
|
||||||
# name = data.get("container_name")
|
|
||||||
# if name is None:
|
|
||||||
# raise KeyError
|
|
||||||
# return cls(
|
|
||||||
# tuple(command) if command else None,
|
|
||||||
# name,
|
|
||||||
# tuple(entry) if entry else None,
|
|
||||||
# data.get("environment"),
|
|
||||||
# data["image"],
|
|
||||||
# _get_labels(data),
|
|
||||||
# None,
|
|
||||||
# cls.get_nets(data),
|
|
||||||
# "unless-stopped",
|
|
||||||
# _get_sec_opts(data),
|
|
||||||
# data.get("user"),
|
|
||||||
# frozenset(volumes) if volumes else None,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# def is_valid(self) -> bool:
|
|
||||||
# attrs = (self.container_name, self.restart, self.logging)
|
|
||||||
# for attr in attrs:
|
|
||||||
# if attr is None:
|
|
||||||
# return False
|
|
||||||
# return True
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
@abstractmethod
|
|
||||||
def get_nets(data: T_Yaml) -> frozenset[T_net] | None:
|
|
||||||
pass
|
|
||||||
# return self._get_nets(data)
|
|
||||||
|
|
||||||
|
|
||||||
class Service(ServiceAbc[Literal["proxy", "internal"], ServiceYaml]):
|
|
||||||
@override
|
|
||||||
@staticmethod
|
|
||||||
def get_nets(data: ServiceYaml) -> frozenset[Literal["proxy", "internal"]] | None:
|
|
||||||
return _get_nets(data)
|
|
||||||
|
|
||||||
|
|
||||||
class TraefikService(ServiceAbc[str, TraefikServiceYaml]):
|
|
||||||
@override
|
|
||||||
@staticmethod
|
|
||||||
def get_nets(data: TraefikServiceYaml) -> frozenset[str] | None:
|
|
||||||
return _get_nets(data)
|
|
||||||
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def _get_nets(
|
|
||||||
data: ServiceYaml,
|
|
||||||
) -> frozenset[Literal["proxy", "internal"]] | None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def _get_nets(data: TraefikServiceYaml) -> frozenset[str] | None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _get_nets(
|
|
||||||
data: ServiceYaml | TraefikServiceYaml,
|
|
||||||
) -> frozenset[str] | frozenset[Literal["proxy", "internal"]] | None:
|
|
||||||
nets = data.get("networks")
|
|
||||||
if nets is None:
|
|
||||||
return
|
|
||||||
return frozenset(nets)
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
from typing import cast
|
|
||||||
|
|
||||||
from compose.service.entity import ServiceYaml
|
|
||||||
from compose.util import read_yml, validate_typed_dict
|
|
||||||
|
|
||||||
|
|
||||||
def services_yaml_factory(path: Path):
|
|
||||||
data = cast(ServiceYaml, read_yml(path))
|
|
||||||
validate_typed_dict(ServiceYaml, data, path)
|
|
||||||
return data
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
from collections.abc import Iterable, Iterator
|
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
from compose.net.entities import Net
|
|
||||||
from compose.net.factory import net_factory_re
|
|
||||||
from compose.service.entity import Service
|
|
||||||
|
|
||||||
|
|
||||||
def _networks_sub(
|
|
||||||
services: Iterable[Service],
|
|
||||||
) -> Iterator[Literal["proxy", "internal"]]:
|
|
||||||
for app_data in services:
|
|
||||||
networks = app_data.networks
|
|
||||||
if networks is None:
|
|
||||||
continue
|
|
||||||
yield from networks
|
|
||||||
|
|
||||||
|
|
||||||
def services_get_networks(services: Iterable[Service]) -> Net | None:
|
|
||||||
networks = frozenset(_networks_sub(services))
|
|
||||||
if not networks:
|
|
||||||
return None
|
|
||||||
return net_factory_re(
|
|
||||||
"internal" in networks,
|
|
||||||
"proxy" in networks,
|
|
||||||
)
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import final
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class SrcPaths:
|
|
||||||
cfg_dir: Path
|
|
||||||
cfg_file: Path
|
|
||||||
env_file: Path
|
|
||||||
|
|
||||||
|
|
||||||
def src_paths_factory(src: Path) -> SrcPaths:
|
|
||||||
return SrcPaths(
|
|
||||||
cfg_dir=src,
|
|
||||||
cfg_file=src.joinpath("cfg.yml"),
|
|
||||||
env_file=src.joinpath(".env"),
|
|
||||||
)
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
from compose.cfg.entity import CfgDataYaml
|
|
||||||
from compose.src_path.entity import SrcPaths
|
|
||||||
|
|
||||||
|
|
||||||
def src_path_get_services(src_paths: SrcPaths, data: CfgDataYaml):
|
|
||||||
for path in data["services"]:
|
|
||||||
yield src_paths.cfg_dir.joinpath(path)
|
|
||||||
|
|
||||||
|
|
||||||
def src_path_get_volumes(src_paths: SrcPaths, data: CfgDataYaml):
|
|
||||||
vols = data.get("volumes")
|
|
||||||
if vols is None:
|
|
||||||
return
|
|
||||||
for path in vols:
|
|
||||||
yield src_paths.cfg_dir.joinpath(path)
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
from collections.abc import Iterator
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import final
|
|
||||||
|
|
||||||
from compose.compose.entity import Compose, TraefikCompose
|
|
||||||
from compose.template.val_obj import (
|
|
||||||
DataDir,
|
|
||||||
NameVal,
|
|
||||||
OrgVal,
|
|
||||||
RecordCls,
|
|
||||||
T_RecordCls,
|
|
||||||
Url,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class ReplaceArgs:
|
|
||||||
org: RecordCls[OrgVal]
|
|
||||||
name: RecordCls[NameVal]
|
|
||||||
org_name: RecordCls[NameVal]
|
|
||||||
data: RecordCls[DataDir]
|
|
||||||
url: RecordCls[Url]
|
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[T_RecordCls]:
|
|
||||||
yield self.org
|
|
||||||
yield self.name
|
|
||||||
yield self.org_name
|
|
||||||
yield self.data
|
|
||||||
yield self.url
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class Template:
|
|
||||||
compose: Compose | TraefikCompose
|
|
||||||
replace_args: ReplaceArgs | None
|
|
||||||
# dest_paths: DestPaths
|
|
||||||
volumes: frozenset[str] | None
|
|
||||||
# proxy_net: str | None
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
from collections.abc import Iterator
|
|
||||||
|
|
||||||
from compose.cfg import DATA_ROOT
|
|
||||||
from compose.cfg.entity import CfgData, OrgData
|
|
||||||
from compose.compose.entity import Compose, TraefikCompose
|
|
||||||
from compose.compose.get import compose_get_volumes
|
|
||||||
from compose.template.entity import ReplaceArgs, Template
|
|
||||||
from compose.template.val_obj import DataDir, NameVal, OrgVal, Record, Url
|
|
||||||
|
|
||||||
|
|
||||||
def replace_args_factory(cfg_data: CfgData, org_data: OrgData) -> ReplaceArgs:
|
|
||||||
_org = OrgVal(org_data.org)
|
|
||||||
_name = NameVal(cfg_data.name)
|
|
||||||
org_name = (
|
|
||||||
NameVal(f"{_org.to_str()}_{_name.to_str()}") if _org.is_valid() else _name
|
|
||||||
)
|
|
||||||
data_dir = DATA_ROOT.joinpath(_org.to_str(), _name.to_str())
|
|
||||||
|
|
||||||
return ReplaceArgs(
|
|
||||||
Record("org", _org),
|
|
||||||
Record("name", _name),
|
|
||||||
Record("org_name", org_name),
|
|
||||||
Record("data", DataDir(data_dir)),
|
|
||||||
Record("url", Url(org_data.url)),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def template_factory(compose: Compose | TraefikCompose) -> Iterator[Template]:
|
|
||||||
cfg_data = compose.cfg
|
|
||||||
vols = frozenset(compose_get_volumes(compose))
|
|
||||||
if not vols:
|
|
||||||
vols = None
|
|
||||||
|
|
||||||
for org_data in cfg_data.orgs:
|
|
||||||
args = replace_args_factory(
|
|
||||||
cfg_data,
|
|
||||||
org_data,
|
|
||||||
)
|
|
||||||
|
|
||||||
yield Template(
|
|
||||||
compose,
|
|
||||||
args,
|
|
||||||
vols,
|
|
||||||
)
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
from collections.abc import Iterable
|
|
||||||
from functools import partial
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from compose.net.entities import Net
|
|
||||||
from compose.template.entity import Template
|
|
||||||
from compose.template.util import replace
|
|
||||||
|
|
||||||
|
|
||||||
def template_get_vols(template: Template) -> Iterable[Path]:
|
|
||||||
def _lambda(x: str) -> str:
|
|
||||||
return x
|
|
||||||
|
|
||||||
vols = template.volumes
|
|
||||||
if not vols:
|
|
||||||
return
|
|
||||||
r_args = template.replace_args
|
|
||||||
re = _lambda if r_args is None else partial(replace, r_args.data)
|
|
||||||
for vol in vols:
|
|
||||||
yield Path(re(vol))
|
|
||||||
|
|
||||||
|
|
||||||
def template_get_proxy(template: Template) -> str | None:
|
|
||||||
proxy = template.compose.networks
|
|
||||||
if proxy is None:
|
|
||||||
return
|
|
||||||
if not isinstance(proxy, Net):
|
|
||||||
return
|
|
||||||
if proxy.proxy is None:
|
|
||||||
return
|
|
||||||
if proxy.internal is None:
|
|
||||||
net = proxy.proxy.name.replace("_proxy", "")
|
|
||||||
else:
|
|
||||||
net = proxy.internal.name
|
|
||||||
r_args = template.replace_args
|
|
||||||
if r_args is None:
|
|
||||||
return net
|
|
||||||
return replace(r_args.name, net)
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
from compose.template.val_obj import T_RecordCls
|
|
||||||
|
|
||||||
|
|
||||||
def replace(record: T_RecordCls, string: str) -> str:
|
|
||||||
return str.replace(string, record.name, record.val.to_str())
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Protocol, final
|
|
||||||
|
|
||||||
from compose.util import get_replace_name
|
|
||||||
|
|
||||||
# class RecordVal(ABC):
|
|
||||||
# @abstractmethod
|
|
||||||
# def to_str(self) -> str:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
class RecordVal(Protocol):
|
|
||||||
def to_str(self) -> str: ...
|
|
||||||
|
|
||||||
|
|
||||||
# TCo_RecordVal = TypeVar(
|
|
||||||
# "TCo_RecordVal",
|
|
||||||
# bound=RecordVal,
|
|
||||||
# covariant=True,
|
|
||||||
# )
|
|
||||||
# TCon_RecordVal = TypeVar(
|
|
||||||
# "TCon_RecordVal",
|
|
||||||
# bound=RecordVal,
|
|
||||||
# contravariant=True,
|
|
||||||
# )
|
|
||||||
@final
|
|
||||||
class T_RecordCls(Protocol):
|
|
||||||
# name: str
|
|
||||||
# val: RecordVal
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def val(self) -> RecordVal: ...
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class RecordCls[T: RecordVal]:
|
|
||||||
name: str
|
|
||||||
val: T
|
|
||||||
|
|
||||||
# @final
|
|
||||||
# class RecordClsProto(Protocol):
|
|
||||||
# name:str
|
|
||||||
# val: RecordVal
|
|
||||||
|
|
||||||
|
|
||||||
# def replace(self:RecordCls[TCo_RecordVal], string: str) -> str:
|
|
||||||
# return str.replace(string, self.name, self.val.to_str())
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class OrgVal:
|
|
||||||
val: str | None
|
|
||||||
|
|
||||||
def to_str(self) -> str:
|
|
||||||
if self.val is None:
|
|
||||||
return "personal"
|
|
||||||
return self.val
|
|
||||||
|
|
||||||
def is_valid(self) -> bool:
|
|
||||||
return self.val is not None
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class NameVal:
|
|
||||||
val: str
|
|
||||||
|
|
||||||
def to_str(self) -> str:
|
|
||||||
return self.val
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class DataDir:
|
|
||||||
path: Path
|
|
||||||
|
|
||||||
def to_str(self) -> str:
|
|
||||||
return str(self.path)
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@dataclass(frozen=True, slots=True)
|
|
||||||
class Url:
|
|
||||||
sub_url: str | None
|
|
||||||
|
|
||||||
def to_str(self) -> str:
|
|
||||||
if self.sub_url is None:
|
|
||||||
return ""
|
|
||||||
return ".".join([self.sub_url, "ccamper7", "net"])
|
|
||||||
|
|
||||||
|
|
||||||
def Record[T: RecordVal](name: str, val: T) -> RecordCls[T]:
|
|
||||||
return RecordCls(get_replace_name(name), val)
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import re
|
|
||||||
from collections.abc import KeysView, Mapping
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, ClassVar, Protocol, cast, override
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
from compose.cfg import T_PrimDict, T_Primitive, T_PrimVal, T_YamlDict
|
|
||||||
|
|
||||||
|
|
||||||
class VerboseSafeDumper(yaml.SafeDumper):
|
|
||||||
@override
|
|
||||||
def ignore_aliases(self, data: Any) -> bool: # pyright: ignore[reportExplicitAny, reportAny]
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def merge_dicts[T: Mapping[Any, Any]](dict1: T, dict2: T) -> T:
|
|
||||||
def _merge_dicts(dict1: T_PrimDict, dict2: T_PrimDict):
|
|
||||||
s1 = frozenset(dict1.keys())
|
|
||||||
s2 = frozenset(dict2.keys())
|
|
||||||
for k in s1.difference(s2):
|
|
||||||
yield k, dict1[k]
|
|
||||||
for k in s2.difference(s1):
|
|
||||||
yield k, dict2[k]
|
|
||||||
for k in s1.intersection(s2):
|
|
||||||
v1 = dict1[k]
|
|
||||||
v2 = dict2[k]
|
|
||||||
if isinstance(v1, dict) and isinstance(v2, dict):
|
|
||||||
yield k, dict[T_Primitive, T_PrimVal](_merge_dicts(v1, v2))
|
|
||||||
continue
|
|
||||||
if isinstance(v1, list) and isinstance(v2, list):
|
|
||||||
yield k, list(frozenset(v1).union(v2))
|
|
||||||
continue
|
|
||||||
raise Exception("merge error")
|
|
||||||
|
|
||||||
return cast(T, dict(_merge_dicts(dict1, dict2)))
|
|
||||||
|
|
||||||
|
|
||||||
def read_yml(path: Path) -> T_YamlDict:
|
|
||||||
with path.open("rt") as f:
|
|
||||||
return cast(T_YamlDict, yaml.safe_load(f))
|
|
||||||
|
|
||||||
|
|
||||||
def to_yaml(data: T_YamlDict) -> str:
|
|
||||||
_yaml = yaml.dump(data, Dumper=VerboseSafeDumper)
|
|
||||||
return re.sub(r"(^\s*-)", r" \g<1>", _yaml, flags=re.MULTILINE)
|
|
||||||
|
|
||||||
|
|
||||||
def get_replace_name(name: str) -> str:
|
|
||||||
return f"${{_{name.upper()}}}"
|
|
||||||
|
|
||||||
|
|
||||||
class T_TypedDict(Protocol):
|
|
||||||
__required_keys__: ClassVar[frozenset[str]]
|
|
||||||
|
|
||||||
def keys(self) -> KeysView[str]: ...
|
|
||||||
|
|
||||||
|
|
||||||
def validate_typed_dict(
|
|
||||||
typed_dict: type[T_TypedDict],
|
|
||||||
data: T_TypedDict,
|
|
||||||
path: Path | None = None,
|
|
||||||
pre: tuple[str, ...] | None = None,
|
|
||||||
) -> None:
|
|
||||||
req = typed_dict.__required_keys__.difference(data.keys())
|
|
||||||
if not req:
|
|
||||||
return
|
|
||||||
if pre is None:
|
|
||||||
keys = (f'"{key}"' for key in req)
|
|
||||||
else:
|
|
||||||
key_pre = ".".join(pre)
|
|
||||||
keys = (f'"{key_pre}.{key}"' for key in req)
|
|
||||||
msg = f"key(s) ({', '.join(keys)}) not found"
|
|
||||||
if path is not None:
|
|
||||||
msg = f"{msg} in file {path!s}"
|
|
||||||
print(msg)
|
|
||||||
raise KeyError
|
|
||||||
30
src/docker_compose/__init__.py
Normal file
30
src/docker_compose/__init__.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path("/nas")
|
||||||
|
TEMPLATE_ROOT = ROOT.joinpath("docker_templates")
|
||||||
|
APP_ROOT = ROOT.joinpath("apps")
|
||||||
|
TRAEFIK_PATH = TEMPLATE_ROOT.joinpath("traefik")
|
||||||
|
|
||||||
|
# ENGINE = create_engine("sqlite://", echo=True)
|
||||||
|
|
||||||
|
#
|
||||||
|
# _ = logger.add("logs/app.log", level="DEBUG", rotation="1 second", retention=10)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def log_cls(obj: type | object, **kwargs: str | int | bool):
|
||||||
|
# logger.debug(
|
||||||
|
# "\n\t".join(
|
||||||
|
# chain(
|
||||||
|
# (
|
||||||
|
# f"created\n\tcls: {obj.__name__ if isclass(obj) else type(obj).__name__}",
|
||||||
|
# ),
|
||||||
|
# (f"{k}: {v}" for k, v in kwargs.items()),
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def init_db():
|
||||||
|
# SQLModel.metadata.create_all(ENGINE)
|
||||||
|
#
|
||||||
|
|
||||||
18
src/docker_compose/__main__.py
Normal file
18
src/docker_compose/__main__.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
from docker_compose.application.write_files import write_template
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
write_template('gitea')
|
||||||
|
# init_db()
|
||||||
|
# _ = render_all()
|
||||||
|
# renderers = RenderByOrg.from_path(TRAEFIK_PATH)
|
||||||
|
# traefik =renderers["util"]
|
||||||
|
# data = traefik.template.compose_data.as_dict
|
||||||
|
# nets = frozenset(render_all())
|
||||||
|
# data["networks"] = {net: NetArgsYaml(name=f"{net}_proxy") for net in nets}
|
||||||
|
# data["services"]["traefik"]["networks"] = nets
|
||||||
|
# data = cast(TypeYamlCompatibleDict, cast(object, data))
|
||||||
|
#
|
||||||
|
# txt = traefik.write(to_yaml(data), render=True)
|
||||||
|
# renderers.write_bind_vol_data()
|
||||||
|
# traefik.bind_vols()
|
||||||
0
src/docker_compose/application/__init__.py
Normal file
0
src/docker_compose/application/__init__.py
Normal file
6
src/docker_compose/application/write_files.py
Normal file
6
src/docker_compose/application/write_files.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from docker_compose.domain.paths.src import SrcPaths
|
||||||
|
|
||||||
|
|
||||||
|
def write_template(app:str):
|
||||||
|
data = SrcPaths.from_name(app)
|
||||||
|
data.compose()
|
||||||
0
src/docker_compose/domain/__init__.py
Normal file
0
src/docker_compose/domain/__init__.py
Normal file
0
src/docker_compose/domain/compose/__init__.py
Normal file
0
src/docker_compose/domain/compose/__init__.py
Normal file
64
src/docker_compose/domain/compose/compose.py
Normal file
64
src/docker_compose/domain/compose/compose.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import ChainMap
|
||||||
|
from collections.abc import Iterator
|
||||||
|
from typing import TYPE_CHECKING, Any, Final, TypedDict, final, override
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from autoslot import Slots
|
||||||
|
|
||||||
|
from docker_compose.domain.compose.service.networks import NetworkDict, NetworkDictSub
|
||||||
|
from docker_compose.domain.compose.service.service import Service, ServiceWriteDict
|
||||||
|
from docker_compose.domain.compose.volume_files import VolumeFile
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from docker_compose.domain.paths.src import SrcPaths
|
||||||
|
|
||||||
|
|
||||||
|
class ComposeDict(TypedDict):
|
||||||
|
name: str
|
||||||
|
services: dict[str, ServiceWriteDict]
|
||||||
|
networks: dict[str, NetworkDictSub]
|
||||||
|
volumes: dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class Compose(Slots):
|
||||||
|
def __init__(self, src_paths: SrcPaths) -> None:
|
||||||
|
self.src_paths: Final[SrcPaths] = src_paths
|
||||||
|
self.name: Final[str] = self.src_paths.path.stem
|
||||||
|
self.services: Final[tuple[Service, ...]] = tuple(self.service_files)
|
||||||
|
self.volumes: Final[tuple[VolumeFile, ...]] = tuple(self.volume_files)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def service_files(self) -> Iterator[Service]:
|
||||||
|
for path in self.src_paths.service_files:
|
||||||
|
yield Service(self, path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def volume_files(self) -> Iterator[VolumeFile]:
|
||||||
|
for path in self.src_paths.volume_files:
|
||||||
|
yield VolumeFile(path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def networks(self) -> Iterator[NetworkDict]:
|
||||||
|
for service in self.services:
|
||||||
|
for network in service.networks:
|
||||||
|
yield network.as_dict
|
||||||
|
|
||||||
|
@property
|
||||||
|
def as_dict(self) -> ComposeDict:
|
||||||
|
return {
|
||||||
|
"name": self.name,
|
||||||
|
"services": dict(ChainMap(*(s.as_dict for s in self.services))),
|
||||||
|
"networks": dict(ChainMap(*(self.networks))),
|
||||||
|
"volumes": dict(ChainMap(*(vol.as_dict for vol in self.volumes))),
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return yaml.dump(self.as_dict)
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
with self.src_paths.compose_file.open("wt") as f:
|
||||||
|
_ = f.write(str(self))
|
||||||
16
src/docker_compose/domain/compose/service/__init__.py
Normal file
16
src/docker_compose/domain/compose/service/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from docker_compose import APP_ROOT
|
||||||
|
from docker_compose.domain.paths import ReStrings
|
||||||
|
from docker_compose.util import ReplaceStr
|
||||||
|
|
||||||
|
DN = ReplaceStr(
|
||||||
|
ReplaceStr.fmt("dn"),
|
||||||
|
"_".join(ReplaceStr.fmt(s) for s in (ReStrings.ORG, ReStrings.APP)),
|
||||||
|
)
|
||||||
|
FQDN = ReplaceStr(
|
||||||
|
ReplaceStr.fmt("fqdn"),
|
||||||
|
"_".join(ReplaceStr.fmt(s) for s in (ReStrings.ORG, ReStrings.APP, ReStrings.SERVICE)),
|
||||||
|
)
|
||||||
|
DATA_DN = ReplaceStr(
|
||||||
|
ReplaceStr.fmt("data"),
|
||||||
|
str(APP_ROOT.joinpath(*(ReplaceStr.fmt(s) for s in (ReStrings.ORG, ReStrings.APP)))),
|
||||||
|
)
|
||||||
31
src/docker_compose/domain/compose/service/networks.py
Normal file
31
src/docker_compose/domain/compose/service/networks.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Final, TypedDict, final
|
||||||
|
|
||||||
|
from autoslot import Slots
|
||||||
|
|
||||||
|
from docker_compose.domain.compose.service import DN
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from docker_compose.domain.compose.service.service import Service
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkDictSub(TypedDict):
|
||||||
|
name: str
|
||||||
|
external: bool
|
||||||
|
|
||||||
|
|
||||||
|
type NetworkDict = dict[str, NetworkDictSub]
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class Network(Slots):
|
||||||
|
def __init__(self, service: Service, val: str) -> None:
|
||||||
|
self.service: Final[Service] = service
|
||||||
|
self.val: Final[str] = val.strip()
|
||||||
|
self.name: Final[str] = f"{DN.repl}_{self.val}"
|
||||||
|
self.external: Final[bool] = "proxy" in self.val
|
||||||
|
|
||||||
|
@property
|
||||||
|
def as_dict(self) -> NetworkDict:
|
||||||
|
return {self.name: NetworkDictSub(name=self.name, external=self.external)}
|
||||||
16
src/docker_compose/domain/compose/service/port.py
Normal file
16
src/docker_compose/domain/compose/service/port.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from typing import Final, final, override
|
||||||
|
|
||||||
|
from autoslot import Slots
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class Port(Slots):
|
||||||
|
sep = ":"
|
||||||
|
def __init__(self, raw:str) -> None:
|
||||||
|
src, dest = (int(s) for s in raw.split(self.sep))
|
||||||
|
self.src: Final[int] = src
|
||||||
|
self.dest: Final[int]=dest
|
||||||
|
|
||||||
|
@override
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.src}{self.sep}{self.dest}"
|
||||||
180
src/docker_compose/domain/compose/service/service.py
Normal file
180
src/docker_compose/domain/compose/service/service.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Generator, Iterable, Iterator
|
||||||
|
from enum import StrEnum
|
||||||
|
from functools import reduce
|
||||||
|
from itertools import chain
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, Final, Literal, NotRequired, TypedDict, final
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from autoslot import Slots
|
||||||
|
from pydantic import TypeAdapter
|
||||||
|
|
||||||
|
from docker_compose.domain.compose.service import DN, FQDN
|
||||||
|
from docker_compose.domain.compose.service.networks import Network
|
||||||
|
from docker_compose.domain.compose.service.port import Port
|
||||||
|
from docker_compose.domain.compose.service.volumes import Volumes
|
||||||
|
from docker_compose.util import ReplaceStr
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from docker_compose.domain.compose.compose import Compose
|
||||||
|
|
||||||
|
|
||||||
|
class DependsOnDict(TypedDict):
|
||||||
|
condition: Literal[
|
||||||
|
"service_started", "service_healthy", "service_completed_successfully"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class HealthCheck(TypedDict):
|
||||||
|
test: tuple[str, ...]
|
||||||
|
interval: str | None
|
||||||
|
timeout: str | None
|
||||||
|
retries: int | None
|
||||||
|
start_period: str | None
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceReadKeys(StrEnum):
|
||||||
|
image = "image"
|
||||||
|
restart = "restart"
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceReadDict(TypedDict):
|
||||||
|
image: str
|
||||||
|
restart: NotRequired[str]
|
||||||
|
user: NotRequired[str]
|
||||||
|
shm_size: NotRequired[str]
|
||||||
|
depends_on: NotRequired[str | dict[str, DependsOnDict]]
|
||||||
|
command: NotRequired[tuple[str, ...]]
|
||||||
|
entrypoint: NotRequired[tuple[str, ...]]
|
||||||
|
environment: NotRequired[dict[str, str]]
|
||||||
|
labels: NotRequired[tuple[str, ...]]
|
||||||
|
logging: NotRequired[tuple[str, ...]]
|
||||||
|
networks: NotRequired[tuple[str, ...]]
|
||||||
|
security_opts: NotRequired[tuple[str, ...]]
|
||||||
|
volumes: NotRequired[tuple[str, ...]]
|
||||||
|
ports: NotRequired[tuple[str, ...]]
|
||||||
|
healthcheck: NotRequired[HealthCheck]
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceWriteDict(TypedDict):
|
||||||
|
container_name: str
|
||||||
|
image: str
|
||||||
|
restart: str
|
||||||
|
user: str | None
|
||||||
|
shm_size: str | None
|
||||||
|
depends_on: str | dict[str, DependsOnDict] | None
|
||||||
|
command: tuple[str, ...]
|
||||||
|
entrypoint: tuple[str, ...]
|
||||||
|
environment: dict[str, str]
|
||||||
|
labels: tuple[str, ...]
|
||||||
|
logging: tuple[str, ...]
|
||||||
|
networks: tuple[str, ...]
|
||||||
|
security_opts: tuple[str, ...]
|
||||||
|
volumes: tuple[str, ...]
|
||||||
|
ports: tuple[str, ...]
|
||||||
|
healthcheck: HealthCheck | None
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class Service(Slots):
|
||||||
|
_traefik_labels: tuple[str, ...] = (
|
||||||
|
"traefik.enable=true",
|
||||||
|
f"traefik.http.routers.{DN.repl}.rule=Host(`{ReplaceStr.fmt('url')}`)",
|
||||||
|
f"traefik.http.routers.{DN.repl}.entrypoints=websecure",
|
||||||
|
f"traefik.docker.network={DN.repl}_proxy",
|
||||||
|
f"traefik.http.routers.{DN.repl}.tls.certresolver=le",
|
||||||
|
)
|
||||||
|
_sec_opts: tuple[str, ...] = ("no-new-privileges:true",)
|
||||||
|
|
||||||
|
def __init__(self, compose: Compose, path: Path) -> None:
|
||||||
|
self.compose: Final[Compose] = compose
|
||||||
|
self.service_name: Final[str] = path.stem
|
||||||
|
|
||||||
|
data: Final[ServiceReadDict] = self.load(path)
|
||||||
|
|
||||||
|
self.container_name: Final[str] = f"{DN.repl}_{self.service_name}"
|
||||||
|
|
||||||
|
self.image: Final[str] = data["image"]
|
||||||
|
self.user: Final[str | None] = data.get("user")
|
||||||
|
self.shm_size: Final[str | None] = data.get("shm_size")
|
||||||
|
self.restart: Final[str] = data.get("restart", "unless-stopped")
|
||||||
|
self.depends_on: Final[str | dict[str, DependsOnDict] | None] = data.get(
|
||||||
|
"depends_on"
|
||||||
|
)
|
||||||
|
self.command: Final[tuple[str, ...]] = self.string_lists(data, "command")
|
||||||
|
self.entrypoint: Final[tuple[str, ...]] = self.string_lists(data, "entrypoint")
|
||||||
|
self.environment: Final[dict[str, str]] = self.string_dict(
|
||||||
|
data.get("environment", {})
|
||||||
|
)
|
||||||
|
self.labels_raw: Final[tuple[str, ...]] = self.string_lists(data, "labels")
|
||||||
|
self.logging: Final[tuple[str, ...]] = self.string_lists(data, "logging")
|
||||||
|
self.networks: Final[tuple[Network, ...]] = tuple(
|
||||||
|
Network(self, s) for s in data.get("networks", ())
|
||||||
|
)
|
||||||
|
self.security_opt: Final[tuple[str, ...]] = self.string_lists(
|
||||||
|
data, "security_opts"
|
||||||
|
)
|
||||||
|
self.volumes: Final[tuple[Volumes, ...]] = tuple(
|
||||||
|
Volumes(self, s) for s in data.get("volumes", ())
|
||||||
|
)
|
||||||
|
self.ports: Final[tuple[Port, ...]] = tuple(
|
||||||
|
Port(s) for s in data.get("ports", ())
|
||||||
|
)
|
||||||
|
self.healthcheck: Final[HealthCheck | None] = data.get("healthcheck")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def as_dict(self) -> dict[str, ServiceWriteDict]:
|
||||||
|
return {
|
||||||
|
self.container_name: ServiceWriteDict(
|
||||||
|
container_name=self.container_name,
|
||||||
|
image=self.image,
|
||||||
|
restart=self.restart,
|
||||||
|
user=self.user,
|
||||||
|
shm_size=self.shm_size,
|
||||||
|
depends_on=self.depends_on,
|
||||||
|
command=self.command,
|
||||||
|
entrypoint=self.entrypoint,
|
||||||
|
environment=self.environment,
|
||||||
|
labels=self.labels,
|
||||||
|
logging=self.logging,
|
||||||
|
networks=tuple(network.val for network in self.networks),
|
||||||
|
security_opts=self.security_opt,
|
||||||
|
volumes=tuple(str(vol) for vol in self.volumes),
|
||||||
|
ports=tuple(str(port) for port in self.ports),
|
||||||
|
healthcheck=self.healthcheck,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def __iter__(self) -> Generator[ReplaceStr]:
|
||||||
|
yield FQDN
|
||||||
|
yield DN
|
||||||
|
yield ReplaceStr(ReplaceStr.fmt("service"), self.service_name)
|
||||||
|
|
||||||
|
def __call__(self, data: str) -> str:
|
||||||
|
return reduce(lambda s, f: f(s), self, data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def labels(self) -> tuple[str, ...]:
|
||||||
|
if "traefik.enable=true" not in self.labels_raw:
|
||||||
|
return self.labels_raw
|
||||||
|
return tuple(chain(self.labels_raw, self._traefik_labels))
|
||||||
|
|
||||||
|
def string_lists(self, data: ServiceReadDict, key: str) -> tuple[str, ...]:
|
||||||
|
return tuple(self.string_lists_sub(data.get(key, ())))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def string_lists_sub(data: Iterable[str]) -> Iterator[str]:
|
||||||
|
for s in data:
|
||||||
|
yield s.strip()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def string_dict(data: dict[str, str]) -> dict[str, str]:
|
||||||
|
return {k.strip(): v.strip() for k, v in data.items()}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load(path: Path) -> ServiceReadDict:
|
||||||
|
with path.open("rt") as f:
|
||||||
|
data = yaml.safe_load(f) # pyright: ignore[reportAny]
|
||||||
|
return TypeAdapter(ServiceReadDict).validate_python(data)
|
||||||
27
src/docker_compose/domain/compose/service/volumes.py
Normal file
27
src/docker_compose/domain/compose/service/volumes.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Final, final, override
|
||||||
|
|
||||||
|
from autoslot import Slots
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from docker_compose.domain.compose.service.service import Service
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class Volumes(Slots):
|
||||||
|
sep = ":"
|
||||||
|
|
||||||
|
def __init__(self, service: Service, raw: str) -> None:
|
||||||
|
src, dest = (s.strip() for s in raw.split(self.sep))
|
||||||
|
self.service: Final[Service] = service
|
||||||
|
self._src: Final[str] = src
|
||||||
|
self.dest: Final[str] = dest
|
||||||
|
|
||||||
|
@property
|
||||||
|
def src(self) -> str:
|
||||||
|
return self.service(self._src)
|
||||||
|
|
||||||
|
@override
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.src}{self.sep}{self.dest}"
|
||||||
21
src/docker_compose/domain/compose/volume_files.py
Normal file
21
src/docker_compose/domain/compose/volume_files.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Final, cast, final
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from autoslot import Slots
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class VolumeFile(Slots):
|
||||||
|
def __init__(self, path: Path) -> None:
|
||||||
|
self.name: Final = path.stem
|
||||||
|
self.data: Final = self.load(path)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load(path: Path) -> dict[str, Any]:
|
||||||
|
with path.open("rt") as f:
|
||||||
|
return cast(dict[str, Any], yaml.safe_load(f))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def as_dict(self) -> dict[str, dict[str, Any]]:
|
||||||
|
return {self.name: self.data}
|
||||||
0
src/docker_compose/domain/env/__init__.py
vendored
Normal file
0
src/docker_compose/domain/env/__init__.py
vendored
Normal file
30
src/docker_compose/domain/env/env_data.py
vendored
Normal file
30
src/docker_compose/domain/env/env_data.py
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
|
from typing import TYPE_CHECKING, Final, final
|
||||||
|
|
||||||
|
from autoslot import Slots
|
||||||
|
|
||||||
|
from docker_compose.domain.env.env_row import EnvRow
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from docker_compose.domain.paths.src import SrcPaths
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class EnvData(Slots):
|
||||||
|
def __init__(self, src_paths: SrcPaths) -> None:
|
||||||
|
self.src_paths: Final = src_paths
|
||||||
|
self.data: Final = tuple(self.lines)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lines(self) -> Generator[EnvRow]:
|
||||||
|
with self.src_paths.env_file.open(mode="rt") as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith("#"):
|
||||||
|
continue
|
||||||
|
yield EnvRow(self, line)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def as_list(self):
|
||||||
|
return tuple(str(row) for row in self.data)
|
||||||
32
src/docker_compose/domain/env/env_row.py
vendored
Normal file
32
src/docker_compose/domain/env/env_row.py
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import secrets
|
||||||
|
from typing import TYPE_CHECKING, Final, final, override
|
||||||
|
|
||||||
|
from autoslot import Slots
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from docker_compose.domain.env.env_data import EnvData
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class EnvRow(Slots):
|
||||||
|
def __init__(self, parent:EnvData, raw:str) -> None:
|
||||||
|
key, val = (s.strip() for s in raw.split("="))
|
||||||
|
|
||||||
|
self.parent:Final = parent
|
||||||
|
self.key:Final= key.strip()
|
||||||
|
self._val:Final = val.strip()
|
||||||
|
if self.key.startswith("#"):
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def val(self) -> str:
|
||||||
|
return self._val.replace("{_PSWD}", secrets.token_urlsafe(12))
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.key}={self.val}"
|
||||||
|
|
||||||
20
src/docker_compose/domain/paths/__init__.py
Normal file
20
src/docker_compose/domain/paths/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from enum import StrEnum
|
||||||
|
|
||||||
|
|
||||||
|
class ReStrings(StrEnum):
|
||||||
|
APP='name'
|
||||||
|
ORG='org'
|
||||||
|
SERVICE='service'
|
||||||
|
URL='url'
|
||||||
|
|
||||||
|
class YAML_EXTS(StrEnum):
|
||||||
|
YML= '.yml'
|
||||||
|
YAML= '.yaml'
|
||||||
|
|
||||||
|
class FILES(StrEnum):
|
||||||
|
COMPOSE= f"docker-compose{YAML_EXTS.YML}"
|
||||||
|
BIND_VOLS= f"bind_volumes{YAML_EXTS.YML}"
|
||||||
|
SERVICES= 'services'
|
||||||
|
VOLUMES= 'volumes'
|
||||||
|
CFG= f"cfg{YAML_EXTS.YML}"
|
||||||
|
ENV= '.env'
|
||||||
21
src/docker_compose/domain/paths/dest.py
Normal file
21
src/docker_compose/domain/paths/dest.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, Final, final
|
||||||
|
|
||||||
|
from autoslot import Slots
|
||||||
|
|
||||||
|
from docker_compose import APP_ROOT
|
||||||
|
from docker_compose.domain.paths import FILES
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from docker_compose.domain.paths.org import OrgData
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class DestPath(Slots):
|
||||||
|
def __init__(self, org_data: OrgData) -> None:
|
||||||
|
self.org_data: Final[OrgData] = org_data
|
||||||
|
self.base_path: Final[Path] = APP_ROOT.joinpath(*self.org_data)
|
||||||
|
self.compose_path: Final[Path] = self.base_path.joinpath(FILES.COMPOSE)
|
||||||
|
self.env_path: Final[Path] = self.base_path.joinpath(FILES.ENV)
|
||||||
76
src/docker_compose/domain/paths/org.py
Normal file
76
src/docker_compose/domain/paths/org.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Generator, Iterator
|
||||||
|
from enum import StrEnum
|
||||||
|
from functools import reduce
|
||||||
|
from typing import TYPE_CHECKING, Final, NotRequired, Self, TypedDict, final
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from autoslot import Slots
|
||||||
|
from pydantic import ConfigDict, TypeAdapter
|
||||||
|
|
||||||
|
from docker_compose.domain.paths import ReStrings
|
||||||
|
from docker_compose.domain.paths.dest import DestPath
|
||||||
|
from docker_compose.domain.render.render import Render
|
||||||
|
from docker_compose.util import ReplaceStr
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from docker_compose.domain.paths.src import SrcPaths
|
||||||
|
|
||||||
|
class Orgs(StrEnum):
|
||||||
|
PERSONAL = "personal"
|
||||||
|
STRYTEN = "stryten"
|
||||||
|
C4 = "c4"
|
||||||
|
|
||||||
|
@final
|
||||||
|
class OrgSub(TypedDict):
|
||||||
|
__pydantic_config__ = ConfigDict(extra='forbid') # pyright: ignore[reportGeneralTypeIssues]
|
||||||
|
url: NotRequired[str]
|
||||||
|
|
||||||
|
type OrgDict = dict[Orgs, OrgSub]
|
||||||
|
|
||||||
|
# type OrgDict = dict[Orgs, OrgSub]
|
||||||
|
|
||||||
|
@final
|
||||||
|
class OrgData(Slots):
|
||||||
|
def __init__(self, src_paths:SrcPaths, app:str, org:str, url: str|None) -> None:
|
||||||
|
if url:
|
||||||
|
url = url.strip()
|
||||||
|
|
||||||
|
self.src_paths: Final = src_paths
|
||||||
|
|
||||||
|
self.app: Final= app.strip()
|
||||||
|
self.org: Final=org.strip()
|
||||||
|
self.url: Final = url if url else None
|
||||||
|
|
||||||
|
self.dest: Final = DestPath(self)
|
||||||
|
self.render: Final=Render(self)
|
||||||
|
|
||||||
|
|
||||||
|
def __call__(self, string: str) -> str:
|
||||||
|
return reduce(lambda s, f: f(s), self.replace_funcs, string)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield self.app
|
||||||
|
yield self.org
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _replace_args(self) -> Generator[tuple[str, str]]:
|
||||||
|
yield ReStrings.APP, self.app
|
||||||
|
yield "org", self.org
|
||||||
|
yield "url", ".".join((self.url, "ccamper7", "net")) if self.url else ""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def replace_funcs(self) -> Generator[ReplaceStr]:
|
||||||
|
for s, r in self._replace_args:
|
||||||
|
yield ReplaceStr(ReplaceStr.fmt(s), r)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_src_path(cls, path: SrcPaths) -> Iterator[Self]:
|
||||||
|
# log_cls(cls, path=str(path))
|
||||||
|
validate = TypeAdapter[OrgDict](OrgDict).validate_python
|
||||||
|
with path.cfg_file.open("rt") as f:
|
||||||
|
data = yaml.safe_load(f) # pyright: ignore[reportAny]
|
||||||
|
app = path.cfg_file.stem
|
||||||
|
for org, _dict in validate(data).items():
|
||||||
|
yield cls(path, app, org, _dict.get('url'))
|
||||||
39
src/docker_compose/domain/paths/src.py
Normal file
39
src/docker_compose/domain/paths/src.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from collections.abc import Iterator
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Final, Self, cast, final
|
||||||
|
|
||||||
|
from autoslot import Slots
|
||||||
|
|
||||||
|
from docker_compose import TEMPLATE_ROOT
|
||||||
|
from docker_compose.domain.compose.compose import Compose
|
||||||
|
from docker_compose.domain.env.env_data import EnvData
|
||||||
|
from docker_compose.domain.paths import FILES, YAML_EXTS
|
||||||
|
from docker_compose.domain.paths.org import OrgData, Orgs
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class SrcPaths(Slots):
|
||||||
|
def __init__(self, path: Path) -> None:
|
||||||
|
self.path: Final = path
|
||||||
|
|
||||||
|
self.compose: Final = Compose(self)
|
||||||
|
self.cfg: Final[dict[Orgs, OrgData]] = {
|
||||||
|
cast(Orgs, obj.org): obj for obj in OrgData.from_src_path(self)
|
||||||
|
}
|
||||||
|
self.env: Final = EnvData(self)
|
||||||
|
self.compose_file: Final = self.path.joinpath(FILES.COMPOSE)
|
||||||
|
self.bind_vol_path: Final = self.path.joinpath(FILES.BIND_VOLS)
|
||||||
|
self.service_files: Final = tuple(self.get_yaml_files(FILES.SERVICES))
|
||||||
|
self.volume_files: Final = tuple(self.get_yaml_files(FILES.VOLUMES))
|
||||||
|
self.cfg_file: Final = self.path.joinpath(FILES.CFG)
|
||||||
|
self.env_file: Final = self.path.joinpath(FILES.ENV)
|
||||||
|
|
||||||
|
def get_yaml_files(self, folder: str) -> Iterator[Path]:
|
||||||
|
for service in self.path.joinpath(folder).iterdir():
|
||||||
|
if service.suffix not in YAML_EXTS:
|
||||||
|
continue
|
||||||
|
yield service
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_name(cls, folder: str) -> Self:
|
||||||
|
return cls(TEMPLATE_ROOT.joinpath(folder))
|
||||||
0
src/docker_compose/domain/render/__init__.py
Normal file
0
src/docker_compose/domain/render/__init__.py
Normal file
32
src/docker_compose/domain/render/bind_vols.py
Normal file
32
src/docker_compose/domain/render/bind_vols.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Iterator
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, Final, final
|
||||||
|
|
||||||
|
from autoslot import Slots
|
||||||
|
|
||||||
|
from docker_compose import ROOT
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from docker_compose.domain.render.render import Render
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class BindVols(Slots):
|
||||||
|
def __init__(self, render: Render) -> None:
|
||||||
|
self.render: Final = render
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
for path in self:
|
||||||
|
path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[Path]:
|
||||||
|
for app in self.render.template.services:
|
||||||
|
for vol in app.volumes:
|
||||||
|
path = Path(self.render.org_data(vol.src))
|
||||||
|
if ROOT not in path.parents:
|
||||||
|
continue
|
||||||
|
if not path.is_dir():
|
||||||
|
continue
|
||||||
|
yield path
|
||||||
30
src/docker_compose/domain/render/render.py
Normal file
30
src/docker_compose/domain/render/render.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Final, final, override
|
||||||
|
|
||||||
|
from autoslot import Slots
|
||||||
|
|
||||||
|
from docker_compose.domain.compose.compose import Compose
|
||||||
|
from docker_compose.domain.render.bind_vols import BindVols
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from docker_compose.domain.paths.org import OrgData
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class Render(Slots):
|
||||||
|
def __init__(self, org_data: OrgData) -> None:
|
||||||
|
self.org_data: Final[OrgData] = org_data
|
||||||
|
self.bind_vols = BindVols(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def template(self) -> Compose:
|
||||||
|
return self.org_data.src_paths.compose
|
||||||
|
|
||||||
|
@override
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.org_data(str(self.template))
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
with self.org_data.dest.compose_path.open("wt") as f:
|
||||||
|
_ = f.write(str(self))
|
||||||
95
src/docker_compose/util/__init__.py
Normal file
95
src/docker_compose/util/__init__.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import re
|
||||||
|
from collections.abc import Iterator, Sequence, Set
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from re import Pattern
|
||||||
|
from typing import Any, Final, final, override
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from autoslot import Slots
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class ReplaceStr:
|
||||||
|
src: str
|
||||||
|
repl: str
|
||||||
|
|
||||||
|
def __call__(self, s: str) -> str:
|
||||||
|
return s.replace(self.src, self.repl)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fmt(src: str) -> str:
|
||||||
|
return f"${{_{src.upper()}}}"
|
||||||
|
|
||||||
|
|
||||||
|
class DictCleanup(Slots):
|
||||||
|
def __init__(self, data: Any) -> None: # pyright: ignore[reportAny]
|
||||||
|
self.data: Final[dict[Any, Any] | Any] = (
|
||||||
|
data.copy() if isinstance(data, dict) else data
|
||||||
|
)
|
||||||
|
|
||||||
|
def __call__(self) -> Any: # pyright: ignore[reportAny]
|
||||||
|
return self.base(self.data) # pyright: ignore[reportAny]
|
||||||
|
|
||||||
|
def base(self, data: Any) -> Any: # pyright: ignore[reportAny]
|
||||||
|
if isinstance(data, tuple | list):
|
||||||
|
return tuple(self.list_prep(data)) # pyright: ignore[reportUnknownArgumentType]
|
||||||
|
if isinstance(data, Set | Iterator):
|
||||||
|
return tuple(sorted(self.list_prep(data)))
|
||||||
|
if isinstance(data, dict):
|
||||||
|
return self.dict_prep(data) # pyright: ignore[reportUnknownArgumentType]
|
||||||
|
return data # pyright: ignore[reportAny]
|
||||||
|
# raise TypeError
|
||||||
|
|
||||||
|
def list_prep(
|
||||||
|
self, data: Set[Any] | Sequence[Any] | Iterator[Any]
|
||||||
|
) -> Iterator[Any]:
|
||||||
|
for val in data: # pyright: ignore[reportAny]
|
||||||
|
if not val and not isinstance(val, bool):
|
||||||
|
continue
|
||||||
|
yield self.base(data)
|
||||||
|
|
||||||
|
# if isinstance(data, (Set,Sequence,Iterator,dict)):
|
||||||
|
# if isinstance(val, tuple|list):
|
||||||
|
# yield tuple(self.list_prep(val))
|
||||||
|
# if isinstance(val, Set|Iterator):
|
||||||
|
# yield tuple(sorted(self.list_prep(val)))
|
||||||
|
# if isinstance(val, dict):
|
||||||
|
# yield dict(self.dict_prep(val))
|
||||||
|
|
||||||
|
def dict_prep(self, data: dict[Any, Any]) -> dict[Any, Any]:
|
||||||
|
for k in tuple(data.keys()): # pyright: ignore[reportAny]
|
||||||
|
data[k] = self.base(data[k])
|
||||||
|
if not data[k] and not isinstance(data[k], bool):
|
||||||
|
del data[k]
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class YamlUtil(Slots):
|
||||||
|
indent: Final[tuple[Pattern[str], str]] = (
|
||||||
|
re.compile(r"(^\s?-)", re.MULTILINE),
|
||||||
|
r" \g<1>",
|
||||||
|
)
|
||||||
|
port: Final[tuple[Pattern[str], str]] = (
|
||||||
|
re.compile(r"(\W*?)(\d+:\d+)", re.MULTILINE),
|
||||||
|
r'\g<1>"\g<2>"',
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, data: dict[Any, Any]) -> None:
|
||||||
|
self.data: Final = DictCleanup(data)() # pyright: ignore[reportAny]
|
||||||
|
|
||||||
|
class VerboseSafeDumper(yaml.SafeDumper):
|
||||||
|
@override
|
||||||
|
def ignore_aliases(self, data: object) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __call__(self) -> str:
|
||||||
|
data = yaml.dump(self.data, Dumper=self.VerboseSafeDumper) # pyright: ignore[reportAny]
|
||||||
|
for regex, repl in self:
|
||||||
|
data = regex.sub(repl, data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[tuple[Pattern[str], str]]:
|
||||||
|
yield self.indent
|
||||||
|
yield self.port
|
||||||
255
uv.lock
generated
Normal file → Executable file
255
uv.lock
generated
Normal file → Executable file
@@ -2,33 +2,109 @@ version = 1
|
|||||||
revision = 3
|
revision = 3
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autoslot"
|
||||||
|
version = "2025.11.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3c/c8/818ff7ed59421f965cc2284ad20686da17e917d0c6d687c2d1b045555527/autoslot-2025.11.1.tar.gz", hash = "sha256:6ccaf4aa6db21e8a4b04a97338fb9efd03c4544cf1c9ac5d0fb12d70a920cb08", size = 11493, upload-time = "2025-11-27T13:32:19.088Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bd/f3/376b4977beeafca00bd9b0cd84e1277c093fe3aac65507054fe025be55c3/autoslot-2025.11.1-py2.py3-none-any.whl", hash = "sha256:fa642b58b082d0021177fcef11dd0634a3fa3e8f078828cade52bc9a44c337f5", size = 8141, upload-time = "2025-11-27T13:32:17.01Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "basedpyright"
|
name = "basedpyright"
|
||||||
version = "1.36.1"
|
version = "1.37.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "nodejs-wheel-binaries" },
|
{ name = "nodejs-wheel-binaries" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/32/29/d42d543a1637e692ac557bfc6d6fcf50e9a7061c1cb4da403378d6a70453/basedpyright-1.36.1.tar.gz", hash = "sha256:20c9a24e2a4c95d5b6d46c78a6b6c7e3dc7cbba227125256431d47c595b15fd4", size = 22834851, upload-time = "2025-12-11T14:55:47.463Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/0c/b0/fbba81ea29eed1274e965cd0445f0d6020b467ff4d3393791e4d6ae02e64/basedpyright-1.37.1.tar.gz", hash = "sha256:1f47bc6f45cbcc5d6f8619d60aa42128e4b38942f5118dcd4bc20c3466c5e02f", size = 25235384, upload-time = "2026-01-08T14:42:46.447Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/c0/7f/f0133313bffa303d32aa74468981eb6b2da7fadda6247c9aa0aeab8391b1/basedpyright-1.36.1-py3-none-any.whl", hash = "sha256:3d738484fe9681cdfe35dd98261f30a9a7aec64208bc91f8773a9aaa9b89dd16", size = 11881725, upload-time = "2025-12-11T14:55:43.805Z" },
|
{ url = "https://files.pythonhosted.org/packages/ad/d6/6b33bb49f08d761d7c958a1e3cecfb3ffbdcf4ba6bbed65b23ab47516b75/basedpyright-1.37.1-py3-none-any.whl", hash = "sha256:caf3adfe54f51623241712f8b4367adb51ef8a8c2288e3e1ec4118319661340d", size = 12297397, upload-time = "2026-01-08T14:42:50.306Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "compose"
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "docker-compose"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "autoslot" },
|
||||||
{ name = "basedpyright" },
|
{ name = "basedpyright" },
|
||||||
|
{ name = "loguru" },
|
||||||
|
{ name = "pydantic" },
|
||||||
{ name = "pyyaml" },
|
{ name = "pyyaml" },
|
||||||
{ name = "ruff" },
|
{ name = "ruff" },
|
||||||
|
{ name = "sqlalchemy" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "basedpyright", specifier = ">=1.36.1" },
|
{ name = "autoslot", specifier = ">=2025.11.1" },
|
||||||
|
{ name = "basedpyright", specifier = ">=1.37.1" },
|
||||||
|
{ name = "loguru", specifier = ">=0.7.3" },
|
||||||
|
{ name = "pydantic", specifier = ">=2.12.5" },
|
||||||
{ name = "pyyaml", specifier = ">=6.0.3" },
|
{ name = "pyyaml", specifier = ">=6.0.3" },
|
||||||
{ name = "ruff", specifier = ">=0.14.9" },
|
{ name = "ruff", specifier = "==0.14.13" },
|
||||||
|
{ name = "sqlalchemy", specifier = ">=2.0.45" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "greenlet"
|
||||||
|
version = "3.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload-time = "2025-12-04T14:32:23.929Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload-time = "2025-12-04T14:23:05.267Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload-time = "2025-12-04T14:50:10.026Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload-time = "2025-12-04T14:57:45.41Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload-time = "2025-12-04T14:26:05.099Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload-time = "2025-12-04T15:04:27.041Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload-time = "2025-12-04T14:27:32.366Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/9a/9030e6f9aa8fd7808e9c31ba4c38f87c4f8ec324ee67431d181fe396d705/greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170", size = 305387, upload-time = "2025-12-04T14:26:51.063Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload-time = "2025-12-04T14:25:20.941Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload-time = "2025-12-04T14:50:11.039Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload-time = "2025-12-04T14:57:47.007Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload-time = "2025-12-04T14:26:06.225Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload-time = "2025-12-04T15:04:28.484Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload-time = "2025-12-04T14:27:33.531Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loguru"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "win32-setctime", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -47,6 +123,74 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/df/af/cd3290a647df567645353feed451ef4feaf5844496ced69c4dcb84295ff4/nodejs_wheel_binaries-24.12.0-py2.py3-none-win_arm64.whl", hash = "sha256:d0c2273b667dd7e3f55e369c0085957b702144b1b04bfceb7ce2411e58333757", size = 39048104, upload-time = "2025-12-11T21:12:23.495Z" },
|
{ url = "https://files.pythonhosted.org/packages/df/af/cd3290a647df567645353feed451ef4feaf5844496ced69c4dcb84295ff4/nodejs_wheel_binaries-24.12.0-py2.py3-none-win_arm64.whl", hash = "sha256:d0c2273b667dd7e3f55e369c0085957b702144b1b04bfceb7ce2411e58333757", size = 39048104, upload-time = "2025-12-11T21:12:23.495Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "2.12.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "annotated-types" },
|
||||||
|
{ name = "pydantic-core" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
{ name = "typing-inspection" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.41.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyyaml"
|
name = "pyyaml"
|
||||||
version = "6.0.3"
|
version = "6.0.3"
|
||||||
@@ -85,26 +229,85 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.14.9"
|
version = "0.14.13"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/1b/ab712a9d5044435be8e9a2beb17cbfa4c241aa9b5e4413febac2a8b79ef2/ruff-0.14.9.tar.gz", hash = "sha256:35f85b25dd586381c0cc053f48826109384c81c00ad7ef1bd977bfcc28119d5b", size = 5809165, upload-time = "2025-12-11T21:39:47.381Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/50/0a/1914efb7903174b381ee2ffeebb4253e729de57f114e63595114c8ca451f/ruff-0.14.13.tar.gz", hash = "sha256:83cd6c0763190784b99650a20fec7633c59f6ebe41c5cc9d45ee42749563ad47", size = 6059504, upload-time = "2026-01-15T20:15:16.918Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/b8/1c/d1b1bba22cffec02351c78ab9ed4f7d7391876e12720298448b29b7229c1/ruff-0.14.9-py3-none-linux_armv6l.whl", hash = "sha256:f1ec5de1ce150ca6e43691f4a9ef5c04574ad9ca35c8b3b0e18877314aba7e75", size = 13576541, upload-time = "2025-12-11T21:39:14.806Z" },
|
{ url = "https://files.pythonhosted.org/packages/c3/ae/0deefbc65ca74b0ab1fd3917f94dc3b398233346a74b8bbb0a916a1a6bf6/ruff-0.14.13-py3-none-linux_armv6l.whl", hash = "sha256:76f62c62cd37c276cb03a275b198c7c15bd1d60c989f944db08a8c1c2dbec18b", size = 13062418, upload-time = "2026-01-15T20:14:50.779Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/94/ab/ffe580e6ea1fca67f6337b0af59fc7e683344a43642d2d55d251ff83ceae/ruff-0.14.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ed9d7417a299fc6030b4f26333bf1117ed82a61ea91238558c0268c14e00d0c2", size = 13779363, upload-time = "2025-12-11T21:39:20.29Z" },
|
{ url = "https://files.pythonhosted.org/packages/47/df/5916604faa530a97a3c154c62a81cb6b735c0cb05d1e26d5ad0f0c8ac48a/ruff-0.14.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:914a8023ece0528d5cc33f5a684f5f38199bbb566a04815c2c211d8f40b5d0ed", size = 13442344, upload-time = "2026-01-15T20:15:07.94Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7d/f8/2be49047f929d6965401855461e697ab185e1a6a683d914c5c19c7962d9e/ruff-0.14.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d5dc3473c3f0e4a1008d0ef1d75cee24a48e254c8bed3a7afdd2b4392657ed2c", size = 12925292, upload-time = "2025-12-11T21:39:38.757Z" },
|
{ url = "https://files.pythonhosted.org/packages/4c/f3/e0e694dd69163c3a1671e102aa574a50357536f18a33375050334d5cd517/ruff-0.14.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d24899478c35ebfa730597a4a775d430ad0d5631b8647a3ab368c29b7e7bd063", size = 12354720, upload-time = "2026-01-15T20:15:09.854Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9e/e9/08840ff5127916bb989c86f18924fd568938b06f58b60e206176f327c0fe/ruff-0.14.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84bf7c698fc8f3cb8278830fb6b5a47f9bcc1ed8cb4f689b9dd02698fa840697", size = 13362894, upload-time = "2025-12-11T21:39:02.524Z" },
|
{ url = "https://files.pythonhosted.org/packages/c3/e8/67f5fcbbaee25e8fc3b56cc33e9892eca7ffe09f773c8e5907757a7e3bdb/ruff-0.14.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aaf3870f14d925bbaf18b8a2347ee0ae7d95a2e490e4d4aea6813ed15ebc80e", size = 12774493, upload-time = "2026-01-15T20:15:20.908Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/31/1c/5b4e8e7750613ef43390bb58658eaf1d862c0cc3352d139cd718a2cea164/ruff-0.14.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa733093d1f9d88a5d98988d8834ef5d6f9828d03743bf5e338bf980a19fce27", size = 13311482, upload-time = "2025-12-11T21:39:17.51Z" },
|
{ url = "https://files.pythonhosted.org/packages/6b/ce/d2e9cb510870b52a9565d885c0d7668cc050e30fa2c8ac3fb1fda15c083d/ruff-0.14.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac5b7f63dd3b27cc811850f5ffd8fff845b00ad70e60b043aabf8d6ecc304e09", size = 12815174, upload-time = "2026-01-15T20:15:05.74Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5b/3a/459dce7a8cb35ba1ea3e9c88f19077667a7977234f3b5ab197fad240b404/ruff-0.14.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a1cfb04eda979b20c8c19550c8b5f498df64ff8da151283311ce3199e8b3648", size = 14016100, upload-time = "2025-12-11T21:39:41.948Z" },
|
{ url = "https://files.pythonhosted.org/packages/88/00/c38e5da58beebcf4fa32d0ddd993b63dfacefd02ab7922614231330845bf/ruff-0.14.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2b1097750d90ba82ce4ba676e85230a0ed694178ca5e61aa9b459970b3eb9", size = 13680909, upload-time = "2026-01-15T20:15:14.537Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a6/31/f064f4ec32524f9956a0890fc6a944e5cf06c63c554e39957d208c0ffc45/ruff-0.14.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1e5cb521e5ccf0008bd74d5595a4580313844a42b9103b7388eca5a12c970743", size = 15477729, upload-time = "2025-12-11T21:39:23.279Z" },
|
{ url = "https://files.pythonhosted.org/packages/61/61/cd37c9dd5bd0a3099ba79b2a5899ad417d8f3b04038810b0501a80814fd7/ruff-0.14.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d0bf87705acbbcb8d4c24b2d77fbb73d40210a95c3903b443cd9e30824a5032", size = 15144215, upload-time = "2026-01-15T20:15:22.886Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7a/6d/f364252aad36ccd443494bc5f02e41bf677f964b58902a17c0b16c53d890/ruff-0.14.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd429a8926be6bba4befa8cdcf3f4dd2591c413ea5066b1e99155ed245ae42bb", size = 15122386, upload-time = "2025-12-11T21:39:33.125Z" },
|
{ url = "https://files.pythonhosted.org/packages/56/8a/85502d7edbf98c2df7b8876f316c0157359165e16cdf98507c65c8d07d3d/ruff-0.14.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3eb5da8e2c9e9f13431032fdcbe7681de9ceda5835efee3269417c13f1fed5c", size = 14706067, upload-time = "2026-01-15T20:14:48.271Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/02/e848787912d16209aba2799a4d5a1775660b6a3d0ab3944a4ccc13e64a02/ruff-0.14.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab208c1b7a492e37caeaf290b1378148f75e13c2225af5d44628b95fd7834273", size = 14497124, upload-time = "2025-12-11T21:38:59.33Z" },
|
{ url = "https://files.pythonhosted.org/packages/7e/2f/de0df127feb2ee8c1e54354dc1179b4a23798f0866019528c938ba439aca/ruff-0.14.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:642442b42957093811cd8d2140dfadd19c7417030a7a68cf8d51fcdd5f217427", size = 14133916, upload-time = "2026-01-15T20:14:57.357Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f3/51/0489a6a5595b7760b5dbac0dd82852b510326e7d88d51dbffcd2e07e3ff3/ruff-0.14.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72034534e5b11e8a593f517b2f2f2b273eb68a30978c6a2d40473ad0aaa4cb4a", size = 14195343, upload-time = "2025-12-11T21:39:44.866Z" },
|
{ url = "https://files.pythonhosted.org/packages/0d/77/9b99686bb9fe07a757c82f6f95e555c7a47801a9305576a9c67e0a31d280/ruff-0.14.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4acdf009f32b46f6e8864af19cbf6841eaaed8638e65c8dac845aea0d703c841", size = 13859207, upload-time = "2026-01-15T20:14:55.111Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f6/53/3bb8d2fa73e4c2f80acc65213ee0830fa0c49c6479313f7a68a00f39e208/ruff-0.14.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:712ff04f44663f1b90a1195f51525836e3413c8a773574a7b7775554269c30ed", size = 14346425, upload-time = "2025-12-11T21:39:05.927Z" },
|
{ url = "https://files.pythonhosted.org/packages/7d/46/2bdcb34a87a179a4d23022d818c1c236cb40e477faf0d7c9afb6813e5876/ruff-0.14.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:591a7f68860ea4e003917d19b5c4f5ac39ff558f162dc753a2c5de897fd5502c", size = 14043686, upload-time = "2026-01-15T20:14:52.841Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ad/04/bdb1d0ab876372da3e983896481760867fc84f969c5c09d428e8f01b557f/ruff-0.14.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a111fee1db6f1d5d5810245295527cda1d367c5aa8f42e0fca9a78ede9b4498b", size = 13258768, upload-time = "2025-12-11T21:39:08.691Z" },
|
{ url = "https://files.pythonhosted.org/packages/1a/a9/5c6a4f56a0512c691cf143371bcf60505ed0f0860f24a85da8bd123b2bf1/ruff-0.14.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:774c77e841cc6e046fc3e91623ce0903d1cd07e3a36b1a9fe79b81dab3de506b", size = 12663837, upload-time = "2026-01-15T20:15:18.921Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/40/d9/8bf8e1e41a311afd2abc8ad12be1b6c6c8b925506d9069b67bb5e9a04af3/ruff-0.14.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8769efc71558fecc25eb295ddec7d1030d41a51e9dcf127cbd63ec517f22d567", size = 13326939, upload-time = "2025-12-11T21:39:53.842Z" },
|
{ url = "https://files.pythonhosted.org/packages/fe/bb/b920016ece7651fa7fcd335d9d199306665486694d4361547ccb19394c44/ruff-0.14.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:61f4e40077a1248436772bb6512db5fc4457fe4c49e7a94ea7c5088655dd21ae", size = 12805867, upload-time = "2026-01-15T20:14:59.272Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/56/a213fa9edb6dd849f1cfbc236206ead10913693c72a67fb7ddc1833bf95d/ruff-0.14.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:347e3bf16197e8a2de17940cd75fd6491e25c0aa7edf7d61aa03f146a1aa885a", size = 13578888, upload-time = "2025-12-11T21:39:35.988Z" },
|
{ url = "https://files.pythonhosted.org/packages/7d/b3/0bd909851e5696cd21e32a8fc25727e5f58f1934b3596975503e6e85415c/ruff-0.14.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6d02f1428357fae9e98ac7aa94b7e966fd24151088510d32cf6f902d6c09235e", size = 13208528, upload-time = "2026-01-15T20:15:03.732Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/33/09/6a4a67ffa4abae6bf44c972a4521337ffce9cbc7808faadede754ef7a79c/ruff-0.14.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7715d14e5bccf5b660f54516558aa94781d3eb0838f8e706fb60e3ff6eff03a8", size = 14314473, upload-time = "2025-12-11T21:39:50.78Z" },
|
{ url = "https://files.pythonhosted.org/packages/3b/3b/e2d94cb613f6bbd5155a75cbe072813756363eba46a3f2177a1fcd0cd670/ruff-0.14.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e399341472ce15237be0c0ae5fbceca4b04cd9bebab1a2b2c979e015455d8f0c", size = 13929242, upload-time = "2026-01-15T20:15:11.918Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/12/0d/15cc82da5d83f27a3c6b04f3a232d61bc8c50d38a6cd8da79228e5f8b8d6/ruff-0.14.9-py3-none-win32.whl", hash = "sha256:df0937f30aaabe83da172adaf8937003ff28172f59ca9f17883b4213783df197", size = 13202651, upload-time = "2025-12-11T21:39:26.628Z" },
|
{ url = "https://files.pythonhosted.org/packages/6a/c5/abd840d4132fd51a12f594934af5eba1d5d27298a6f5b5d6c3be45301caf/ruff-0.14.13-py3-none-win32.whl", hash = "sha256:ef720f529aec113968b45dfdb838ac8934e519711da53a0456038a0efecbd680", size = 12919024, upload-time = "2026-01-15T20:14:43.647Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/32/f7/c78b060388eefe0304d9d42e68fab8cffd049128ec466456cef9b8d4f06f/ruff-0.14.9-py3-none-win_amd64.whl", hash = "sha256:c0b53a10e61df15a42ed711ec0bda0c582039cf6c754c49c020084c55b5b0bc2", size = 14702079, upload-time = "2025-12-11T21:39:11.954Z" },
|
{ url = "https://files.pythonhosted.org/packages/c2/55/6384b0b8ce731b6e2ade2b5449bf07c0e4c31e8a2e68ea65b3bafadcecc5/ruff-0.14.13-py3-none-win_amd64.whl", hash = "sha256:6070bd026e409734b9257e03e3ef18c6e1a216f0435c6751d7a8ec69cb59abef", size = 14097887, upload-time = "2026-01-15T20:15:01.48Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/26/09/7a9520315decd2334afa65ed258fed438f070e31f05a2e43dd480a5e5911/ruff-0.14.9-py3-none-win_arm64.whl", hash = "sha256:8e821c366517a074046d92f0e9213ed1c13dbc5b37a7fc20b07f79b64d62cc84", size = 13744730, upload-time = "2025-12-11T21:39:29.659Z" },
|
{ url = "https://files.pythonhosted.org/packages/4d/e1/7348090988095e4e39560cfc2f7555b1b2a7357deba19167b600fdf5215d/ruff-0.14.13-py3-none-win_arm64.whl", hash = "sha256:7ab819e14f1ad9fe39f246cfcc435880ef7a9390d81a2b6ac7e01039083dd247", size = 13080224, upload-time = "2026-01-15T20:14:45.853Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sqlalchemy"
|
||||||
|
version = "2.0.45"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/be/f9/5e4491e5ccf42f5d9cfc663741d261b3e6e1683ae7812114e7636409fcc6/sqlalchemy-2.0.45.tar.gz", hash = "sha256:1632a4bda8d2d25703fdad6363058d882541bdaaee0e5e3ddfa0cd3229efce88", size = 9869912, upload-time = "2025-12-09T21:05:16.737Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/c8/7cc5221b47a54edc72a0140a1efa56e0a2730eefa4058d7ed0b4c4357ff8/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe187fc31a54d7fd90352f34e8c008cf3ad5d064d08fedd3de2e8df83eb4a1cf", size = 3277082, upload-time = "2025-12-09T22:11:06.167Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/50/80a8d080ac7d3d321e5e5d420c9a522b0aa770ec7013ea91f9a8b7d36e4a/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:672c45cae53ba88e0dad74b9027dddd09ef6f441e927786b05bec75d949fbb2e", size = 3293131, upload-time = "2025-12-09T22:13:52.626Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/4c/13dab31266fc9904f7609a5dc308a2432a066141d65b857760c3bef97e69/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:470daea2c1ce73910f08caf10575676a37159a6d16c4da33d0033546bddebc9b", size = 3225389, upload-time = "2025-12-09T22:11:08.093Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/04/891b5c2e9f83589de202e7abaf24cd4e4fa59e1837d64d528829ad6cc107/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9c6378449e0940476577047150fd09e242529b761dc887c9808a9a937fe990c8", size = 3266054, upload-time = "2025-12-09T22:13:54.262Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/24/fc59e7f71b0948cdd4cff7a286210e86b0443ef1d18a23b0d83b87e4b1f7/sqlalchemy-2.0.45-cp313-cp313-win32.whl", hash = "sha256:4b6bec67ca45bc166c8729910bd2a87f1c0407ee955df110d78948f5b5827e8a", size = 2110299, upload-time = "2025-12-09T21:39:33.486Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/c5/d17113020b2d43073412aeca09b60d2009442420372123b8d49cc253f8b8/sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl", hash = "sha256:afbf47dc4de31fa38fd491f3705cac5307d21d4bb828a4f020ee59af412744ee", size = 2136264, upload-time = "2025-12-09T21:39:36.801Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/8d/bb40a5d10e7a5f2195f235c0b2f2c79b0bf6e8f00c0c223130a4fbd2db09/sqlalchemy-2.0.45-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83d7009f40ce619d483d26ac1b757dfe3167b39921379a8bd1b596cf02dab4a6", size = 3521998, upload-time = "2025-12-09T22:13:28.622Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/a5/346128b0464886f036c039ea287b7332a410aa2d3fb0bb5d404cb8861635/sqlalchemy-2.0.45-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d8a2ca754e5415cde2b656c27900b19d50ba076aa05ce66e2207623d3fe41f5a", size = 3473434, upload-time = "2025-12-09T22:13:30.188Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/64/4e1913772646b060b025d3fc52ce91a58967fe58957df32b455de5a12b4f/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f46ec744e7f51275582e6a24326e10c49fbdd3fc99103e01376841213028774", size = 3272404, upload-time = "2025-12-09T22:11:09.662Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/27/caf606ee924282fe4747ee4fd454b335a72a6e018f97eab5ff7f28199e16/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:883c600c345123c033c2f6caca18def08f1f7f4c3ebeb591a63b6fceffc95cce", size = 3277057, upload-time = "2025-12-09T22:13:56.213Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/d0/3d64218c9724e91f3d1574d12eb7ff8f19f937643815d8daf792046d88ab/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2c0b74aa79e2deade948fe8593654c8ef4228c44ba862bb7c9585c8e0db90f33", size = 3222279, upload-time = "2025-12-09T22:11:11.1Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/10/dd7688a81c5bc7690c2a3764d55a238c524cd1a5a19487928844cb247695/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a420169cef179d4c9064365f42d779f1e5895ad26ca0c8b4c0233920973db74", size = 3244508, upload-time = "2025-12-09T22:13:57.932Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/41/db75756ca49f777e029968d9c9fee338c7907c563267740c6d310a8e3f60/sqlalchemy-2.0.45-cp314-cp314-win32.whl", hash = "sha256:e50dcb81a5dfe4b7b4a4aa8f338116d127cb209559124f3694c70d6cd072b68f", size = 2113204, upload-time = "2025-12-09T21:39:38.365Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/a2/0e1590e9adb292b1d576dbcf67ff7df8cf55e56e78d2c927686d01080f4b/sqlalchemy-2.0.45-cp314-cp314-win_amd64.whl", hash = "sha256:4748601c8ea959e37e03d13dcda4a44837afcd1b21338e637f7c935b8da06177", size = 2138785, upload-time = "2025-12-09T21:39:39.503Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/39/f05f0ed54d451156bbed0e23eb0516bcad7cbb9f18b3bf219c786371b3f0/sqlalchemy-2.0.45-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd337d3526ec5298f67d6a30bbbe4ed7e5e68862f0bf6dd21d289f8d37b7d60b", size = 3522029, upload-time = "2025-12-09T22:13:32.09Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/0f/d15398b98b65c2bce288d5ee3f7d0a81f77ab89d9456994d5c7cc8b2a9db/sqlalchemy-2.0.45-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9a62b446b7d86a3909abbcd1cd3cc550a832f99c2bc37c5b22e1925438b9367b", size = 3475142, upload-time = "2025-12-09T22:13:33.739Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0", size = 1936672, upload-time = "2025-12-09T21:54:52.608Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.15.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-inspection"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "win32-setctime"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" },
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user