Skip to content

Commit 798c2d8

Browse files
committed
make_ver now split into parts
make_ver tests now run from uv Starting thinking about uv Dockerfile (unfinished)
1 parent a826bcb commit 798c2d8

10 files changed

Lines changed: 374 additions & 309 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
api/
1+
**/.venv
2+
*.py[cod]
3+
**/__*/**

make_ver/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.venv
2+
uv.lock

make_ver/Dockerfile

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
FROM python:alpine
1+
FROM astral/uv:alpine AS base
2+
ENV UV_PROJECT_ENVIRONMENT=/.uv.venv
3+
ENV UV_NO_SYNC=True
4+
WORKDIR /make_ver/
5+
FROM base AS dependencies
6+
COPY pyproject.toml uv.lock ./
7+
RUN UV_NO_SYNC=False uv sync --no-dev
8+
FROM dependencies AS test_dependencies
9+
RUN UV_NO_SYNC=False uv sync
210

3-
WORKDIR /app/
11+
FROM dependencies AS code
12+
COPY . .
413

5-
RUN pip install \
6-
--no-cache \
7-
--root-user-action ignore \
8-
falcon \
9-
pytest \
10-
&& true
14+
FROM test_dependencies AS test
15+
COPY --from=code /make_ver/ .
16+
CMD [ "uv", "run", "pytest" ]
17+
FROM code AS production
18+
CMD [ "uv", "run", "api" ]

make_ver/Dockerfile.old

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM python:alpine
2+
3+
WORKDIR /app/
4+
5+
RUN pip install \
6+
--no-cache \
7+
--root-user-action ignore \
8+
falcon \
9+
pytest \
10+
&& true

make_ver/Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ test_docker:
3030

3131
build_static_local:
3232
python3 api.py ../static/projects/ ../static/language_reference/languages/ --export
33-
run_local:
34-
python3 -m pdb -c continue api.py ../static/projects/ ../static/language_reference/languages/
33+
serve_local:
34+
uv run -m pdb -c continue api.py ../static/projects/ ../static/language_reference/languages/
3535
# http://localhost:8000/static/index.html
3636
# http://localhost:8000/api/v1/language_reference.json
37-
test_local:
38-
PYTHONPATH=./ pytest --doctest-modules
37+
test:
38+
uv run pytest

make_ver/api.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
import falcon
66

77
import _falcon_helpers
8-
from make_ver2 import ProjectVersions, LanguageVersions, LANGUAGES
8+
9+
from .make_ver import LANGUAGES
10+
from .language_versions import LanguageVersions
11+
from .project_versions import ProjectVersions
912

1013
log = logging.getLogger(__name__)
1114

@@ -101,12 +104,12 @@ def create_wsgi_app(project_path=None, language_path=None, **kwargs):
101104

102105
# Export ----------------------------------------------------------------------
103106

104-
def export():
105-
from falcon import testing
106-
test_client = testing.TestClient(app)
107+
def export(output_path: Path = Path()) -> None:
108+
from falcon import testing as falcon_testing
109+
test_client = falcon_testing.TestClient(app)
107110
def read_write(url):
108111
log.info(url)
109-
path = Path(url.strip('/'))
112+
path = output_path.joinpath(url.strip('/'))
110113
path.parent.mkdir(parents=True, exist_ok=True)
111114
with path.open('wt', encoding="utf-8") as filehandle:
112115
data = test_client.simulate_get(url)
@@ -136,7 +139,7 @@ def get_args():
136139
parser.add_argument('--host', action='store', default='0.0.0.0', help='')
137140
parser.add_argument('--port', action='store', default=8000, type=int, help='')
138141

139-
parser.add_argument('--export', action='store_true')
142+
parser.add_argument('--export', action='store', type=Path)
140143

141144
parser.add_argument('--log_level', action='store', type=int, help='loglevel of output to stdout', default=logging.INFO)
142145

@@ -154,7 +157,7 @@ def get_args():
154157
app = create_wsgi_app(**kwargs)
155158

156159
if kwargs['export']:
157-
export()
160+
export(kwargs['export'])
158161
exit()
159162

160163
try:

make_ver/language_versions.py

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import io
2+
from collections import defaultdict
3+
from collections.abc import Iterable, Sequence
4+
from functools import cached_property, reduce
5+
from itertools import chain
6+
from pathlib import Path
7+
from types import MappingProxyType
8+
9+
from .make_ver import LANGUAGES, Language, Version, VersionModel
10+
11+
12+
class LanguageVersions():
13+
r"""
14+
>>> from .make_ver import _testfiles
15+
>>> with _testfiles() as files:
16+
... l = LanguageVersions(files)
17+
18+
`hello_world` is in the VERSION_ORDER and so will come first, then the rest are then ordered
19+
>>> l.all_versions
20+
('hello_world', 'test1', 'test2', 'test4')
21+
>>> l.languages['js']['test4']
22+
'console.log("Hello Test")'
23+
"""
24+
25+
VERSION_ORDER = [ # TODO: these should be moved eventually
26+
'title',
27+
'download',
28+
'help',
29+
'run',
30+
'hello_world',
31+
'read_line_from_console',
32+
'comment',
33+
'define_variables',
34+
'define_constants',
35+
'arithmetic',
36+
'if_statement',
37+
'if_statement_more',
38+
'while_loop',
39+
'until_loop',
40+
'for_loop',
41+
'for_each_loop',
42+
'file_write',
43+
'file_read',
44+
'string_concatenation',
45+
'split_strings',
46+
'convert_string_to_integer_and_back',
47+
'convert_double_to_string_and_back',
48+
'function',
49+
'function_with_return_value',
50+
'function_with_params_by_reference',
51+
'function_with_params_by_value',
52+
'function_with_param_function',
53+
'define_fixed_array',
54+
'define_list',
55+
'define_2d_arrays_with_nested_arrays',
56+
'define_2d_arrays_with_1d_array_with_lookup_function',
57+
'define_2d_arrays_with_dictionary',
58+
'define_map',
59+
'define_set',
60+
'error_handling',
61+
'join_strings',
62+
'random_number',
63+
'class',
64+
'read_csv_into_array_of_classs',
65+
'sleep',
66+
'list_comprehension',
67+
'dict_comprehension',
68+
]
69+
70+
def __init__(self, files: Iterable[Path]):
71+
"""
72+
Concept:
73+
`/java/main_stuff.java`
74+
`/java/graphics_stuff.java`
75+
`/java/network_stuff.java`
76+
are amalgamated/concatenated into `self.files['java']`
77+
This means that we can have
78+
`hello_world`
79+
`draw_sqaure`
80+
`get_http`
81+
defined in different files but still available as a version
82+
"""
83+
def _amalgamate_files_with_same_extension(acc, path):
84+
acc[''.join(path.suffixes).strip('.')].append(path.read_text('utf-8'))
85+
return acc
86+
self.files = MappingProxyType({
87+
ext: "\n".join(file_content_list)
88+
for ext, file_content_list in reduce(
89+
_amalgamate_files_with_same_extension,
90+
files,
91+
defaultdict(list),
92+
).items()
93+
})
94+
95+
@property
96+
def all_versions(self) -> Sequence[Version]:
97+
versions = frozenset(chain.from_iterable(version_data.keys() for version_data in self.languages.values()))
98+
return tuple(v for v in self.VERSION_ORDER if v in versions) + tuple(sorted(v for v in versions if v not in self.VERSION_ORDER))
99+
100+
@cached_property
101+
def languages(self) -> MappingProxyType[str, MappingProxyType[Version, str]]:
102+
return MappingProxyType({
103+
language: self._build_versions(io.StringIO(self.files.get(language)), LANGUAGES[language])
104+
for language in LANGUAGES.keys()
105+
})
106+
107+
@classmethod
108+
def _build_versions(cls, source: io.IOBase, language: Language) -> MappingProxyType[Version, str]:
109+
r"""
110+
>>> from textwrap import dedent
111+
>>> java = io.StringIO(dedent('''
112+
... import java.util.stream.Collectors; // VER: list_comprehension,dict_comprehension
113+
... import static java.util.Map.entry; // VER: dict_comprehension
114+
... public class Java {
115+
... public static void main(String[] args) {new Java();}
116+
... public Java() {
117+
... hello_world();
118+
... arithmetic();
119+
... }
120+
... void hello_world() {
121+
... // // Must be in file named `HelloWorld.java` // VER: hello_world
122+
... //public class HelloWorld { // VER: hello_world
123+
... //public static void main(String[] args) {new HelloWorld();} // VER: hello_world
124+
... //public HelloWorld() { // VER: hello_world
125+
... System.out.println("Hello World"); // VER: hello_world
126+
... //} // VER: hello_world
127+
... //} // VER: hello_world
128+
... }
129+
... void list_comprehension() {
130+
... List<Integer> data1 = new ArrayList<>(Arrays.asList(new Integer[]{1,2,3,4,5,6})); // VER: list_comprehension
131+
... }
132+
... void dict_comprehension() {
133+
... Map<String,Integer> data3 = Map.ofEntries( // VER: dict_comprehension
134+
... entry("a", 1), // VER: dict_comprehension
135+
... entry("b", 2) // VER: dict_comprehension
136+
... ); // VER: dict_comprehension
137+
... }
138+
... }
139+
... '''))
140+
>>> versions = LanguageVersions._build_versions(java, LANGUAGES['java'])
141+
142+
>>> sorted(versions.keys())
143+
['dict_comprehension', 'hello_world', 'list_comprehension']
144+
145+
>>> print(versions['hello_world'])
146+
// Must be in file named `HelloWorld.java`
147+
public class HelloWorld {
148+
public static void main(String[] args) {new HelloWorld();}
149+
public HelloWorld() {
150+
System.out.println("Hello World");
151+
}
152+
}
153+
154+
>>> print(versions['list_comprehension'])
155+
import java.util.stream.Collectors;
156+
List<Integer> data1 = new ArrayList<>(Arrays.asList(new Integer[]{1,2,3,4,5,6}));
157+
158+
>>> print(versions['dict_comprehension'])
159+
import java.util.stream.Collectors;
160+
import static java.util.Map.entry;
161+
Map<String,Integer> data3 = Map.ofEntries(
162+
entry("a", 1),
163+
entry("b", 2)
164+
);
165+
"""
166+
lines = VersionModel(source, language).lines
167+
# We build a dict incrementally with the versions from each line.
168+
# This is not the way ProjectVersions works.
169+
# Perhaps we can get a list of all versions and then run the version evaluator for each line to include it?
170+
# The process below is definitely efficient to build, but I wonder if a single code path for versions would be neater and cleaner
171+
# For now LanguageVersions feels like it's own case, but it feels weird because `VER:` lines are used for different things in different ways
172+
def _reducer(acc: defaultdict[Version, list[str]], line: VersionModel.Line) -> defaultdict[Version, list[str]]:
173+
for version in line.version_evaluator.versions:
174+
if not version: # Lines with not explicitly tagged with a version are not considered in LanguageVersions
175+
continue
176+
acc[version].append(line.line_without_ver)
177+
return acc
178+
return MappingProxyType({
179+
version: "\n".join(lines)
180+
for version, lines in reduce(_reducer, lines, defaultdict(list[str])).items()
181+
})
182+
183+
# @classmethod
184+
# def build_versions_from_path(cls: Self, path: str | Path) -> MappingProxyType[Version, str]:
185+
# path = Path(path)
186+
# language = LANGUAGES.get(path.suffix.strip('.'))
187+
# assert language, f'Language unknown: {path.suffix}. Valid languages are {LANGUAGES.keys()}'
188+
# with path.open() as source:
189+
# return cls._build_versions(source, language)

0 commit comments

Comments
 (0)