mtg-card-puller 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mtg_card_puller-0.0.1/.dockerignore +3 -0
- mtg_card_puller-0.0.1/.gitignore +112 -0
- mtg_card_puller-0.0.1/.pre-commit-config.yaml +9 -0
- mtg_card_puller-0.0.1/LICENSE +16 -0
- mtg_card_puller-0.0.1/Makefile +128 -0
- mtg_card_puller-0.0.1/PKG-INFO +118 -0
- mtg_card_puller-0.0.1/README.md +78 -0
- mtg_card_puller-0.0.1/dockerfile +17 -0
- mtg_card_puller-0.0.1/environment.yaml +8 -0
- mtg_card_puller-0.0.1/mtg_card_puller/__init__.py +5 -0
- mtg_card_puller-0.0.1/mtg_card_puller/cli.py +23 -0
- mtg_card_puller-0.0.1/mtg_card_puller/mtg_card_puller.py +100 -0
- mtg_card_puller-0.0.1/notebooks/scratch.ipynb +79 -0
- mtg_card_puller-0.0.1/pyproject.toml +52 -0
- mtg_card_puller-0.0.1/scripts/README.md +3 -0
- mtg_card_puller-0.0.1/tests/__init__.py +1 -0
- mtg_card_puller-0.0.1/tests/test_mtg_card_puller.py +51 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Template Specific
|
|
2
|
+
data/*
|
|
3
|
+
!data/README.md
|
|
4
|
+
|
|
5
|
+
# IDE
|
|
6
|
+
.vscode/
|
|
7
|
+
|
|
8
|
+
# Byte-compiled / optimized / DLL files
|
|
9
|
+
__pycache__/
|
|
10
|
+
*.py[cod]
|
|
11
|
+
*$py.class
|
|
12
|
+
|
|
13
|
+
# C extensions
|
|
14
|
+
*.so
|
|
15
|
+
|
|
16
|
+
# Distribution / packaging
|
|
17
|
+
.Python
|
|
18
|
+
env/
|
|
19
|
+
build/
|
|
20
|
+
develop-eggs/
|
|
21
|
+
dist/
|
|
22
|
+
downloads/
|
|
23
|
+
eggs/
|
|
24
|
+
.eggs/
|
|
25
|
+
lib/
|
|
26
|
+
lib64/
|
|
27
|
+
parts/
|
|
28
|
+
sdist/
|
|
29
|
+
var/
|
|
30
|
+
wheels/
|
|
31
|
+
*.egg-info/
|
|
32
|
+
.installed.cfg
|
|
33
|
+
*.egg
|
|
34
|
+
|
|
35
|
+
# PyInstaller
|
|
36
|
+
# Usually these files are written by a python script from a template
|
|
37
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
38
|
+
*.manifest
|
|
39
|
+
*.spec
|
|
40
|
+
|
|
41
|
+
# Installer logs
|
|
42
|
+
pip-log.txt
|
|
43
|
+
pip-delete-this-directory.txt
|
|
44
|
+
|
|
45
|
+
# Unit test / coverage reports
|
|
46
|
+
htmlcov/
|
|
47
|
+
.tox/
|
|
48
|
+
.coverage
|
|
49
|
+
.coverage.*
|
|
50
|
+
.cache
|
|
51
|
+
nosetests.xml
|
|
52
|
+
coverage.xml
|
|
53
|
+
*.cover
|
|
54
|
+
.hypothesis/
|
|
55
|
+
.pytest_cache/
|
|
56
|
+
|
|
57
|
+
# Translations
|
|
58
|
+
*.mo
|
|
59
|
+
*.pot
|
|
60
|
+
|
|
61
|
+
# Django stuff:
|
|
62
|
+
*.log
|
|
63
|
+
local_settings.py
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# pyenv
|
|
82
|
+
.python-version
|
|
83
|
+
|
|
84
|
+
# celery beat schedule file
|
|
85
|
+
celerybeat-schedule
|
|
86
|
+
|
|
87
|
+
# SageMath parsed files
|
|
88
|
+
*.sage.py
|
|
89
|
+
|
|
90
|
+
# dotenv
|
|
91
|
+
.env
|
|
92
|
+
|
|
93
|
+
# virtualenv
|
|
94
|
+
.venv
|
|
95
|
+
venv/
|
|
96
|
+
ENV/
|
|
97
|
+
|
|
98
|
+
# Spyder project settings
|
|
99
|
+
.spyderproject
|
|
100
|
+
.spyproject
|
|
101
|
+
|
|
102
|
+
# Rope project settings
|
|
103
|
+
.ropeproject
|
|
104
|
+
|
|
105
|
+
# mkdocs documentation
|
|
106
|
+
/site
|
|
107
|
+
|
|
108
|
+
# mypy
|
|
109
|
+
.mypy_cache/
|
|
110
|
+
|
|
111
|
+
# IDE settings
|
|
112
|
+
.vscode/
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Apache Software License 2.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Rob Fletcher
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
.PHONY: clean clean-test clean-pyc clean-build docs help
|
|
2
|
+
.DEFAULT_GOAL := help
|
|
3
|
+
|
|
4
|
+
define BROWSER_PYSCRIPT
|
|
5
|
+
import os, webbrowser, sys
|
|
6
|
+
|
|
7
|
+
from urllib.request import pathname2url
|
|
8
|
+
|
|
9
|
+
webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1])))
|
|
10
|
+
endef
|
|
11
|
+
export BROWSER_PYSCRIPT
|
|
12
|
+
|
|
13
|
+
define PRINT_HELP_PYSCRIPT
|
|
14
|
+
import re, sys
|
|
15
|
+
|
|
16
|
+
for line in sys.stdin:
|
|
17
|
+
match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
|
|
18
|
+
if match:
|
|
19
|
+
target, help = match.groups()
|
|
20
|
+
print("%-20s %s" % (target, help))
|
|
21
|
+
endef
|
|
22
|
+
export PRINT_HELP_PYSCRIPT
|
|
23
|
+
|
|
24
|
+
BROWSER := python -c "$$BROWSER_PYSCRIPT"
|
|
25
|
+
|
|
26
|
+
help:
|
|
27
|
+
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
|
|
28
|
+
|
|
29
|
+
clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
|
|
30
|
+
|
|
31
|
+
clean-build: ## remove build artifacts
|
|
32
|
+
rm -fr build/
|
|
33
|
+
rm -fr dist/
|
|
34
|
+
rm -fr .eggs/
|
|
35
|
+
find . -name '*.egg-info' -exec rm -fr {} +
|
|
36
|
+
find . -name '*.egg' -exec rm -f {} +
|
|
37
|
+
|
|
38
|
+
clean-pyc: ## remove Python file artifacts
|
|
39
|
+
find . -name '*.pyc' -exec rm -f {} +
|
|
40
|
+
find . -name '*.pyo' -exec rm -f {} +
|
|
41
|
+
find . -name '*~' -exec rm -f {} +
|
|
42
|
+
find . -name '__pycache__' -exec rm -fr {} +
|
|
43
|
+
|
|
44
|
+
clean-test: ## remove test and coverage artifacts
|
|
45
|
+
rm -fr .tox/
|
|
46
|
+
rm -f .coverage
|
|
47
|
+
rm -fr htmlcov/
|
|
48
|
+
rm -fr .pytest_cache
|
|
49
|
+
|
|
50
|
+
lint: ## check style with flake8
|
|
51
|
+
flake8 mtg_card_puller tests
|
|
52
|
+
|
|
53
|
+
test: ## run tests quickly with the default Python
|
|
54
|
+
pytest
|
|
55
|
+
|
|
56
|
+
test-all: ## run tests on every Python version with tox
|
|
57
|
+
tox
|
|
58
|
+
|
|
59
|
+
coverage: ## check code coverage quickly with the default Python
|
|
60
|
+
coverage run --source mtg_card_puller -m pytest
|
|
61
|
+
coverage report -m
|
|
62
|
+
coverage html
|
|
63
|
+
$(BROWSER) htmlcov/index.html
|
|
64
|
+
|
|
65
|
+
docs: ## generate Sphinx HTML documentation, including API docs
|
|
66
|
+
rm -f docs/mtg_card_puller.rst
|
|
67
|
+
rm -f docs/modules.rst
|
|
68
|
+
sphinx-apidoc -o docs/ mtg_card_puller
|
|
69
|
+
$(MAKE) -C docs clean
|
|
70
|
+
$(MAKE) -C docs html
|
|
71
|
+
$(BROWSER) docs/_build/html/index.html
|
|
72
|
+
|
|
73
|
+
servedocs: docs ## compile the docs watching for changes
|
|
74
|
+
watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D .
|
|
75
|
+
|
|
76
|
+
release: dist ## package and upload a release
|
|
77
|
+
twine upload dist/*
|
|
78
|
+
|
|
79
|
+
dist: clean ## builds source and wheel package
|
|
80
|
+
python setup.py sdist
|
|
81
|
+
python setup.py bdist_wheel
|
|
82
|
+
ls -l dist
|
|
83
|
+
|
|
84
|
+
install: clean ## install the package to the active Python's site-packages
|
|
85
|
+
python setup.py install
|
|
86
|
+
|
|
87
|
+
SHELL:=/bin/bash
|
|
88
|
+
model_dir := models/$(model)
|
|
89
|
+
step_dir := models/$(model)/$(step)
|
|
90
|
+
image_name := $(model).$(step):latest
|
|
91
|
+
dockerfile := $(step_dir)/Dockerfile
|
|
92
|
+
|
|
93
|
+
model-build:
|
|
94
|
+
@if [ -z "$(model)" ]; then \
|
|
95
|
+
echo "must specify a 'model' and 'step'"; \
|
|
96
|
+
exit 1; \
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
docker build \
|
|
100
|
+
--progress=plain \
|
|
101
|
+
--ssh=default \
|
|
102
|
+
-t $(image_name) \
|
|
103
|
+
-f $(dockerfile) \
|
|
104
|
+
$(step_dir)
|
|
105
|
+
|
|
106
|
+
model-run:
|
|
107
|
+
@if [ -z "$(model)" ]; then \
|
|
108
|
+
echo "must specify a 'model' and 'step'"; \
|
|
109
|
+
exit 1; \
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
cd $(step_dir) && docker compose up --force-recreate run-local
|
|
113
|
+
|
|
114
|
+
model-shell:
|
|
115
|
+
@if [ -z "$(model)" ]; then \
|
|
116
|
+
echo "must specify a 'model' and 'step'"; \
|
|
117
|
+
exit 1; \
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
cd $(step_dir) && docker compose run --entrypoint bash run-local
|
|
121
|
+
|
|
122
|
+
model-dev:
|
|
123
|
+
@if [ -z "$(model)" ]; then \
|
|
124
|
+
echo "must specify a 'model' and 'step'"; \
|
|
125
|
+
exit 1; \
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
cd $(step_dir) && docker compose up --force-recreate dev
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mtg_card_puller
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Pull MTG card images in b
|
|
5
|
+
Project-URL: Homepage, https://github.com/seerai/mtg_card_puller
|
|
6
|
+
Author-email: Rob Fletcher <robroy.fletcher@gmail.com>
|
|
7
|
+
License: Apache Software License 2.0
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2025, Rob Fletcher
|
|
10
|
+
|
|
11
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
+
you may not use this file except in compliance with the License.
|
|
13
|
+
You may obtain a copy of the License at
|
|
14
|
+
|
|
15
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
|
|
17
|
+
Unless required by applicable law or agreed to in writing, software
|
|
18
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
+
See the License for the specific language governing permissions and
|
|
21
|
+
limitations under the License.
|
|
22
|
+
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
25
|
+
Classifier: Intended Audience :: Developers
|
|
26
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
27
|
+
Classifier: Natural Language :: English
|
|
28
|
+
Classifier: Programming Language :: Python :: 3
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
30
|
+
Requires-Python: >=3.10
|
|
31
|
+
Requires-Dist: click>=7.0
|
|
32
|
+
Requires-Dist: geodesic-api
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: coverage; extra == 'dev'
|
|
35
|
+
Requires-Dist: jupyterlab; extra == 'dev'
|
|
36
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
38
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# mtg_card_puller
|
|
42
|
+
|
|
43
|
+
Pull MTG card images in b
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
To setup mtg_card_puller create a conda environment with the generated `environment.yaml`.
|
|
47
|
+
This sets up a development environment with all the required packages. This is what is used when building
|
|
48
|
+
out the functionality of this project.
|
|
49
|
+
```bash
|
|
50
|
+
conda env create -f environment.yaml
|
|
51
|
+
conda activate mtg_puller
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then install the python module. This installs the module the same way as if someone just wanting
|
|
55
|
+
to use this package would install it. This is what is used when using this package as a dependency.
|
|
56
|
+
**Note:** This installs the `dev` extra dependencies. When installing this to use as a standalone package,
|
|
57
|
+
you should leave off the `[dev]` part.
|
|
58
|
+
```bash
|
|
59
|
+
python -m pip install .[dev]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
If functionality from this repo is needed in another project, you can install this package as a dependency
|
|
63
|
+
directly from the GitHub repo.
|
|
64
|
+
```bash
|
|
65
|
+
python -m pip install git+https://github.com/seerai/mtg_card_puller.git
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
You can then test that the docker image builds and runs correctly.
|
|
69
|
+
```bash
|
|
70
|
+
docker build -t mtg_card_puller:latest .
|
|
71
|
+
```
|
|
72
|
+
Once the build is complete, test the image:
|
|
73
|
+
```bash
|
|
74
|
+
docker run --rm mtg_card_puller:latest
|
|
75
|
+
```
|
|
76
|
+
You should see a message printed:
|
|
77
|
+
```
|
|
78
|
+
Replace this message by putting your code into mtg_card_puller.cli.main
|
|
79
|
+
See click documentation at https://click.palletsprojects.com/
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Development
|
|
83
|
+
|
|
84
|
+
### READMEs and Notes
|
|
85
|
+
Every folder in this project other than `mtg_card_puller`` should have a README.md
|
|
86
|
+
file (the template does this automatically for all folders). Each of these READMEs should be kept
|
|
87
|
+
up to date and should describe the contents of the folder. This is especially important for the
|
|
88
|
+
the data folder and should include all data files, their sources and how to download them. This
|
|
89
|
+
is also a good place to put any notes about the contents of the folder. For example, if you are
|
|
90
|
+
working on a notebook and want to save some notes about what you are doing, you can put them in
|
|
91
|
+
the README for that folder. **You should always be asking yourself** "If someone else were to look at
|
|
92
|
+
this folder, would they know what is going on?". If the answer is no, then you should add more
|
|
93
|
+
information to the README.
|
|
94
|
+
|
|
95
|
+
### Dependency Management
|
|
96
|
+
When developing on this project, **make sure that you never directly `pip install` any packages**. This
|
|
97
|
+
leads to situations where peoples environemnts are different and the code will not run correctly
|
|
98
|
+
when another person tries to install the package. Instead, always add the dependency to the
|
|
99
|
+
`pyproject.toml` file and reinstall the package. This ensures that all dependencies are tracked and
|
|
100
|
+
others can use this package without issue. When possible, you should also pin a version or version range
|
|
101
|
+
of the dependency. i.e. `numpy==1.19.2` or `numpy>=1.19.2,<1.20.0`.
|
|
102
|
+
|
|
103
|
+
### Testing
|
|
104
|
+
Any functions implemened in this package should be covered by tests if possible. This doesnt mean that
|
|
105
|
+
every line of code needs to be tested, but that the functionality of the code is tested. Anything
|
|
106
|
+
that is used only for development purposes can sometimes be excluded from testing. However, any
|
|
107
|
+
function/class you expect someone to use after installing this as a package should be tested.
|
|
108
|
+
**You should always be asking yourself** "If someone else were to use this code, would they know that it
|
|
109
|
+
works correctly?". If the answer is no, then you should add tests.
|
|
110
|
+
|
|
111
|
+
This project uses `pytest` for testing. To run the tests, cd to the root of the project and run:
|
|
112
|
+
```bash
|
|
113
|
+
coverage run -m pytest
|
|
114
|
+
```
|
|
115
|
+
This will run all the tests and generate a coverage report. To view the coverage report, run:
|
|
116
|
+
```bash
|
|
117
|
+
coverage report
|
|
118
|
+
```
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# mtg_card_puller
|
|
2
|
+
|
|
3
|
+
Pull MTG card images in b
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
To setup mtg_card_puller create a conda environment with the generated `environment.yaml`.
|
|
7
|
+
This sets up a development environment with all the required packages. This is what is used when building
|
|
8
|
+
out the functionality of this project.
|
|
9
|
+
```bash
|
|
10
|
+
conda env create -f environment.yaml
|
|
11
|
+
conda activate mtg_puller
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Then install the python module. This installs the module the same way as if someone just wanting
|
|
15
|
+
to use this package would install it. This is what is used when using this package as a dependency.
|
|
16
|
+
**Note:** This installs the `dev` extra dependencies. When installing this to use as a standalone package,
|
|
17
|
+
you should leave off the `[dev]` part.
|
|
18
|
+
```bash
|
|
19
|
+
python -m pip install .[dev]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
If functionality from this repo is needed in another project, you can install this package as a dependency
|
|
23
|
+
directly from the GitHub repo.
|
|
24
|
+
```bash
|
|
25
|
+
python -m pip install git+https://github.com/seerai/mtg_card_puller.git
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
You can then test that the docker image builds and runs correctly.
|
|
29
|
+
```bash
|
|
30
|
+
docker build -t mtg_card_puller:latest .
|
|
31
|
+
```
|
|
32
|
+
Once the build is complete, test the image:
|
|
33
|
+
```bash
|
|
34
|
+
docker run --rm mtg_card_puller:latest
|
|
35
|
+
```
|
|
36
|
+
You should see a message printed:
|
|
37
|
+
```
|
|
38
|
+
Replace this message by putting your code into mtg_card_puller.cli.main
|
|
39
|
+
See click documentation at https://click.palletsprojects.com/
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Development
|
|
43
|
+
|
|
44
|
+
### READMEs and Notes
|
|
45
|
+
Every folder in this project other than `mtg_card_puller`` should have a README.md
|
|
46
|
+
file (the template does this automatically for all folders). Each of these READMEs should be kept
|
|
47
|
+
up to date and should describe the contents of the folder. This is especially important for the
|
|
48
|
+
the data folder and should include all data files, their sources and how to download them. This
|
|
49
|
+
is also a good place to put any notes about the contents of the folder. For example, if you are
|
|
50
|
+
working on a notebook and want to save some notes about what you are doing, you can put them in
|
|
51
|
+
the README for that folder. **You should always be asking yourself** "If someone else were to look at
|
|
52
|
+
this folder, would they know what is going on?". If the answer is no, then you should add more
|
|
53
|
+
information to the README.
|
|
54
|
+
|
|
55
|
+
### Dependency Management
|
|
56
|
+
When developing on this project, **make sure that you never directly `pip install` any packages**. This
|
|
57
|
+
leads to situations where peoples environemnts are different and the code will not run correctly
|
|
58
|
+
when another person tries to install the package. Instead, always add the dependency to the
|
|
59
|
+
`pyproject.toml` file and reinstall the package. This ensures that all dependencies are tracked and
|
|
60
|
+
others can use this package without issue. When possible, you should also pin a version or version range
|
|
61
|
+
of the dependency. i.e. `numpy==1.19.2` or `numpy>=1.19.2,<1.20.0`.
|
|
62
|
+
|
|
63
|
+
### Testing
|
|
64
|
+
Any functions implemened in this package should be covered by tests if possible. This doesnt mean that
|
|
65
|
+
every line of code needs to be tested, but that the functionality of the code is tested. Anything
|
|
66
|
+
that is used only for development purposes can sometimes be excluded from testing. However, any
|
|
67
|
+
function/class you expect someone to use after installing this as a package should be tested.
|
|
68
|
+
**You should always be asking yourself** "If someone else were to use this code, would they know that it
|
|
69
|
+
works correctly?". If the answer is no, then you should add tests.
|
|
70
|
+
|
|
71
|
+
This project uses `pytest` for testing. To run the tests, cd to the root of the project and run:
|
|
72
|
+
```bash
|
|
73
|
+
coverage run -m pytest
|
|
74
|
+
```
|
|
75
|
+
This will run all the tests and generate a coverage report. To view the coverage report, run:
|
|
76
|
+
```bash
|
|
77
|
+
coverage report
|
|
78
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
FROM python:3.10-slim AS builder
|
|
2
|
+
|
|
3
|
+
RUN mkdir /install
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
COPY mtg_card_puller/ /app/mtg_card_puller
|
|
6
|
+
COPY pyproject.toml LICENSE README.md /app
|
|
7
|
+
|
|
8
|
+
RUN python -m pip install . --prefix=/install
|
|
9
|
+
|
|
10
|
+
FROM python:3.10-slim
|
|
11
|
+
WORKDIR /app
|
|
12
|
+
COPY --from=builder /install /usr/local
|
|
13
|
+
|
|
14
|
+
ENV PATH="/usr/local/bin:${PATH}"
|
|
15
|
+
ENV PYTHONPATH="${PYTHONPATH}:/usr/local/"
|
|
16
|
+
|
|
17
|
+
CMD ["mtg_card_puller"]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Console script for mtg_card_puller."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import click
|
|
5
|
+
from mtg_card_puller import mtg_card_puller
|
|
6
|
+
|
|
7
|
+
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
|
8
|
+
|
|
9
|
+
@click.command(context_settings=CONTEXT_SETTINGS)
|
|
10
|
+
@click.option(
|
|
11
|
+
"-v", "--verbose", is_flag=True, help="Enable verbose output for debugging"
|
|
12
|
+
)
|
|
13
|
+
@click.option(
|
|
14
|
+
"-f", "--file", type=str, required=True, help="Path to the file containing card names"
|
|
15
|
+
)
|
|
16
|
+
def main(verbose, file):
|
|
17
|
+
"""Console script for mtg_card_puller."""
|
|
18
|
+
mtg_card_puller.main(deck_file=file)
|
|
19
|
+
return 0
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if __name__ == "__main__":
|
|
23
|
+
sys.exit(main()) # pragma: no cover
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Main module."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import tqdm
|
|
5
|
+
import requests
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Tuple
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
def extract_card_id(mox_full_name: str) -> Tuple[str, str, int]:
|
|
14
|
+
"""Extracts the card ID from the full name of a card.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
mox_full_name (str): The full name of the card.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Tuple[str, str, int]: A tuple containing the card name, card series, and card number.
|
|
21
|
+
"""
|
|
22
|
+
# Extract the card ID using regex
|
|
23
|
+
card_name = ""
|
|
24
|
+
card_series = ""
|
|
25
|
+
card_number = ""
|
|
26
|
+
match = re.search(r'\d (.*) \(([A-Z0-9]{3})\) (\d{1,3})', mox_full_name)
|
|
27
|
+
if match:
|
|
28
|
+
card_name = match.group(1)
|
|
29
|
+
card_series = match.group(2)
|
|
30
|
+
card_number = int(match.group(3))
|
|
31
|
+
|
|
32
|
+
logger.debug(f"Extracted card series: {card_series}, card number: {card_number} from full name: {mox_full_name}")
|
|
33
|
+
else:
|
|
34
|
+
raise ValueError("Problem matching card ID from full name: " + mox_full_name)
|
|
35
|
+
|
|
36
|
+
return card_name, card_series, card_number
|
|
37
|
+
|
|
38
|
+
def execute_request(card_name: str, card_series: str, card_number: int) -> None:
|
|
39
|
+
"""Executes a request to the MTG API to fetch card details.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
card_name (str): The name of the card.
|
|
43
|
+
card_series (str): The card series.
|
|
44
|
+
card_number (int): The card number.
|
|
45
|
+
"""
|
|
46
|
+
normalized_name = card_name.strip(".'").replace(" ", "_")
|
|
47
|
+
headers = {'User-Agent': 'mtg_card_puller', 'Accept': '*/*'}
|
|
48
|
+
|
|
49
|
+
# Check if the image already exists
|
|
50
|
+
img_path = Path(f"{normalized_name}.png")
|
|
51
|
+
if img_path.is_file():
|
|
52
|
+
logger.info(f"Image for {card_name} already exists, skipping download.")
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
time.sleep(0.1) # Avoid hitting the API too fast
|
|
56
|
+
r = requests.get(f"https://api.scryfall.com/cards/{card_series}/{card_number}", headers=headers)
|
|
57
|
+
if r.status_code != 200:
|
|
58
|
+
logger.error(f"Failed to fetch card details for {card_name} ({card_series} {card_number})")
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
img_url = r.json().get('image_uris', {}).get('png')
|
|
62
|
+
|
|
63
|
+
time.sleep(0.1) # Avoid hitting the API too fast
|
|
64
|
+
r = requests.get(img_url, stream=True, headers=headers)
|
|
65
|
+
if r.status_code != 200:
|
|
66
|
+
logger.error(f"Failed to fetch image for {card_name} ({card_series} {card_number})")
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
# Save the image to a file
|
|
70
|
+
with open(f"{normalized_name}.png", 'wb') as f:
|
|
71
|
+
for chunk in r.iter_content(1024):
|
|
72
|
+
f.write(chunk)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def main(deck_file: str, verbose: bool = False) -> None:
|
|
76
|
+
"""Main entrypoint for the script
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
deck_file (str): Path to the file containing card names.
|
|
80
|
+
verbose (bool): Enable verbose output for debugging.
|
|
81
|
+
"""
|
|
82
|
+
logger.setLevel(logging.DEBUG if verbose else logging.INFO)
|
|
83
|
+
with open(deck_file, 'r') as f:
|
|
84
|
+
pbar = tqdm.tqdm(f.readlines(), desc="Processing cards", colour="green", unit="card")
|
|
85
|
+
for line in pbar:
|
|
86
|
+
try:
|
|
87
|
+
card_name, card_series, card_number = extract_card_id(line.strip())
|
|
88
|
+
pbar.set_description(f"Fetching {card_name[:20]:20}")
|
|
89
|
+
except ValueError as e:
|
|
90
|
+
logger.error(e)
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
execute_request(card_name, card_series, card_number)
|
|
94
|
+
# time.sleep(1)
|
|
95
|
+
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error(f"Error fetching card details: {e}")
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "code",
|
|
5
|
+
"execution_count": 1,
|
|
6
|
+
"metadata": {},
|
|
7
|
+
"outputs": [],
|
|
8
|
+
"source": [
|
|
9
|
+
"import requests"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"cell_type": "code",
|
|
14
|
+
"execution_count": 4,
|
|
15
|
+
"metadata": {},
|
|
16
|
+
"outputs": [
|
|
17
|
+
{
|
|
18
|
+
"name": "stdout",
|
|
19
|
+
"output_type": "stream",
|
|
20
|
+
"text": [
|
|
21
|
+
"https://cards.scryfall.io/png/front/2/3/23eb3cf7-c90d-4bfa-b125-4fbcb5614468.png?1710673416\n"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"source": [
|
|
26
|
+
"headers = {'User-Agent': 'mtg_card_puller', 'Accept': '*/*'}\n",
|
|
27
|
+
"r = requests.get(\"https://api.scryfall.com/cards/pip/7\", headers=headers)\n",
|
|
28
|
+
"if r.status_code == 200:\n",
|
|
29
|
+
" img_uri = r.json()['image_uris']['png']\n",
|
|
30
|
+
" print(r.json()['image_uris']['png'])\n",
|
|
31
|
+
"else:\n",
|
|
32
|
+
" print(\"Error: \", r.status_code)"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"cell_type": "code",
|
|
37
|
+
"execution_count": 7,
|
|
38
|
+
"metadata": {},
|
|
39
|
+
"outputs": [],
|
|
40
|
+
"source": [
|
|
41
|
+
"r = requests.get(img_uri, headers=headers, stream=True)\n",
|
|
42
|
+
"if r.status_code == 200:\n",
|
|
43
|
+
" with open('card.png', 'wb') as f:\n",
|
|
44
|
+
" for chunk in r.iter_content(1024):\n",
|
|
45
|
+
" f.write(chunk)\n",
|
|
46
|
+
"else:\n",
|
|
47
|
+
" print(\"Error: \", r.status_code)"
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"cell_type": "code",
|
|
52
|
+
"execution_count": null,
|
|
53
|
+
"metadata": {},
|
|
54
|
+
"outputs": [],
|
|
55
|
+
"source": []
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"metadata": {
|
|
59
|
+
"kernelspec": {
|
|
60
|
+
"display_name": "mtg_puller",
|
|
61
|
+
"language": "python",
|
|
62
|
+
"name": "python3"
|
|
63
|
+
},
|
|
64
|
+
"language_info": {
|
|
65
|
+
"codemirror_mode": {
|
|
66
|
+
"name": "ipython",
|
|
67
|
+
"version": 3
|
|
68
|
+
},
|
|
69
|
+
"file_extension": ".py",
|
|
70
|
+
"mimetype": "text/x-python",
|
|
71
|
+
"name": "python",
|
|
72
|
+
"nbconvert_exporter": "python",
|
|
73
|
+
"pygments_lexer": "ipython3",
|
|
74
|
+
"version": "3.10.16"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"nbformat": 4,
|
|
78
|
+
"nbformat_minor": 2
|
|
79
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.21.0"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "mtg_card_puller"
|
|
9
|
+
description = "Pull MTG card images in b"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license.file = "LICENSE"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Rob Fletcher", email = "robroy.fletcher@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
maintaners = [
|
|
16
|
+
{ name = "Rob Fletcher", email = "robroy.fletcher@gmail.com" }
|
|
17
|
+
]
|
|
18
|
+
requires-python = ">=3.10"
|
|
19
|
+
classifiers=[
|
|
20
|
+
'Development Status :: 2 - Pre-Alpha',
|
|
21
|
+
'Intended Audience :: Developers',
|
|
22
|
+
'License :: OSI Approved :: Apache Software License',
|
|
23
|
+
'Natural Language :: English',
|
|
24
|
+
'Programming Language :: Python :: 3',
|
|
25
|
+
'Programming Language :: Python :: 3.10',
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"click>=7.0",
|
|
29
|
+
"geodesic-api"
|
|
30
|
+
]
|
|
31
|
+
dynamic = ["version"]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
mtg_card_puller = "mtg_card_puller.cli:main"
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
dev = [
|
|
38
|
+
"ruff",
|
|
39
|
+
"pytest",
|
|
40
|
+
"coverage",
|
|
41
|
+
"pre-commit",
|
|
42
|
+
"jupyterlab",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Homepage = "https://github.com/seerai/mtg_card_puller"
|
|
47
|
+
|
|
48
|
+
[tool.hatch.version]
|
|
49
|
+
path = "mtg_card_puller/__init__.py"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.sdist]
|
|
52
|
+
exclude = [".github", "data/", "docs/"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Unit test package for mtg_card_puller."""
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
"""Tests for `mtg_card_puller` package."""
|
|
4
|
+
|
|
5
|
+
from mtg_card_puller import mtg_card_puller
|
|
6
|
+
from mtg_card_puller import cli
|
|
7
|
+
from click.testing import CliRunner
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def test_deck_data():
|
|
13
|
+
"""Load sample data
|
|
14
|
+
|
|
15
|
+
Load the sample data file in the data folder to test with
|
|
16
|
+
"""
|
|
17
|
+
with open('tests/data/sample_deck.txt', 'r') as f:
|
|
18
|
+
yield f
|
|
19
|
+
|
|
20
|
+
card_id_cases = [
|
|
21
|
+
("1 Mr. House, President and CEO (PIP) 7 *F*", "PIP", 7),
|
|
22
|
+
("1 Arcane Signet (M3C) 283", "M3C", 283),
|
|
23
|
+
("1 Arid Mesa (MH2) 244", "MH2", 244),
|
|
24
|
+
("1 Attempted Murder (UNF) 66 *F*", "UNF", 66),
|
|
25
|
+
("1 Automated Artificer (NEO) 239 *F*", "NEO", 239),
|
|
26
|
+
("1 Bag of Devouring (AFC) 21", "AFC", 21),
|
|
27
|
+
("1 Barbarian Class (AFR) 131", "AFR", 131),
|
|
28
|
+
("1 Battlefield Forge (C21) 278", "C21", 278),
|
|
29
|
+
("1 Bennie Bracks, Zoologist (NCC) 86", "NCC", 86),
|
|
30
|
+
("1 Berserker's Frenzy (AFC) 29", "AFC", 29),
|
|
31
|
+
("1 Big Score (SNC) 102 *F*", "SNC", 102),
|
|
32
|
+
("1 Blood Crypt (RNA) 245", "RNA", 245),
|
|
33
|
+
("1 Bloodstained Mire (KTK) 230", "KTK", 230),
|
|
34
|
+
("1 Boros Signet (SLD) 291 *E*", "SLD", 291),
|
|
35
|
+
("1 Brazen Dwarf (AFR) 134", "AFR", 134),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
def test_command_line_interface():
|
|
39
|
+
"""Test the CLI."""
|
|
40
|
+
runner = CliRunner()
|
|
41
|
+
result = runner.invoke(cli.main)
|
|
42
|
+
assert result.exit_code == 2
|
|
43
|
+
assert "Missing option '-f'" in result.output
|
|
44
|
+
help_result = runner.invoke(cli.main, ['--help'])
|
|
45
|
+
assert help_result.exit_code == 0
|
|
46
|
+
assert 'Console script for mtg_card_puller' in help_result.output
|
|
47
|
+
|
|
48
|
+
@pytest.mark.parametrize("card_line, expected_series, expected_number", card_id_cases)
|
|
49
|
+
def test_extract_card_id(card_line, expected_series, expected_number):
|
|
50
|
+
assert mtg_card_puller.extract_card_id(card_line)[1:] == (expected_series, expected_number)
|
|
51
|
+
|