Skip to content

Commit d205df4

Browse files
authored
Merge pull request #65 from martindurant/more-create
Implement create for rust, and change to return created files.
2 parents 01e7df3 + 1159946 commit d205df4

File tree

6 files changed

+69
-18
lines changed

6 files changed

+69
-18
lines changed

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ dependencies = [
3232
]
3333

3434
[project.optional-dependencies]
35-
test = ["pytest", "pytest-cov", "django", "streamlit", "copier", "jinja2-time", "flask"]
35+
test = ["pytest", "pytest-cov", "django", "streamlit", "copier", "jinja2-time", "flask",
36+
"maturin"]
3637
qt = ["pyqt>5,<6", "pyqtwebengin>5,<6"]
3738

3839
[project.scripts]

src/projspec/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def make(artifact, path, storage_options, types, xtypes):
5151
proj = projspec.Project(
5252
path, storage_options=storage_options, types=types, xtypes=xtypes
5353
)
54-
proj.make(artifact)
54+
print("Created:", proj.make(artifact))
5555

5656

5757
@main.command()

src/projspec/proj/base.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,16 @@ def __init__(
8383
self.artifacts = AttrDict()
8484
# read and respect .gitignore? for exclude directories?
8585
self.excludes = excludes or default_excludes
86-
self._pyproject = None
87-
self._scanned_files = None
86+
self._reset()
8887
self.resolve(walk=walk, types=types, xtypes=xtypes)
88+
89+
def _reset(self):
90+
"""Prepare this project for parsing with new specs"""
91+
# cached properties
92+
self.__dict__.pop("basenames", None)
93+
self.__dict__.pop("filelist", None)
94+
self.__dict__.pop("pyproject", None)
95+
self._scanned_files = None
8996
# clear cached files
9097
self._scanned_files = None
9198

@@ -368,12 +375,22 @@ def from_dict(dic):
368375
proj.fs, proj.url = fsspec.url_to_fs(proj.path, **proj.storage_options)
369376
return proj
370377

371-
def create(self, name: str):
372-
"""Make this project conform to the given project spec type."""
378+
def create(self, name: str) -> list[str]:
379+
"""Make this project conform to the given project spec type.
380+
381+
Returns a list of files that were created.
382+
"""
373383
cls = get_cls(name)
374384
# causes reparse and makes a new instance
375385
# could rerun resolve or only parse for give type and add, instead.
376-
return cls.create(self.path)
386+
allfiles = self.fs.find(self.url, detail=False)
387+
cls.create(self.url)
388+
allfiles2 = self.fs.find(self.url, detasil=False)
389+
self._reset()
390+
spec = cls(self)
391+
spec.parse()
392+
self.specs[camel_to_snake(cls.__name__)] = spec
393+
return sorted(set(allfiles2) - set(allfiles))
377394

378395
def make(self, qname: str, **kwargs) -> None:
379396
"""Make an artifact of the given type
@@ -467,7 +484,7 @@ def _create(path: str) -> None:
467484
raise NotImplementedError("Subclass must implement this")
468485

469486
@classmethod
470-
def create(cls, path: str) -> Project:
487+
def create(cls, path: str):
471488
"""Make the target directory compliant with this project type, if not already"""
472489
# TODO: implement remote??
473490
# TODO: implement dry-run?
@@ -476,8 +493,6 @@ def create(cls, path: str) -> Project:
476493
os.makedirs(path, exist_ok=True)
477494
if not cls.snake_name() in Project(path):
478495
cls._create(path)
479-
# perhaps should return ProjSpec, but it needs to be added to a project
480-
return Project(path)
481496

482497
def parse(self) -> None:
483498
raise ParseFailed

src/projspec/proj/rust.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import subprocess
2+
13
import toml
24
from projspec.proj import ProjectSpec, PythonLibrary
5+
from projspec.utils import AttrDict
36

47

58
class Rust(ProjectSpec):
@@ -12,12 +15,31 @@ def match(self) -> bool:
1215

1316
def parse(self):
1417
from projspec.content.metadata import DescriptiveMetadata
18+
from projspec.artifact.base import FileArtifact
1519

1620
with self.proj.fs.open(f"{self.proj.url}/Cargo.toml", "rt") as f:
1721
meta = toml.load(f)
1822
self.contents["desciptive_metadata"] = DescriptiveMetadata(
1923
proj=self.proj, meta=meta.get("package")
2024
)
25+
bin = AttrDict()
26+
bin["debug"] = FileArtifact(
27+
proj=self.proj,
28+
cmd=["cargo", "build"],
29+
# extension is platform specific
30+
fn=f"{self.proj.url}/target/debug/{meta['package']['name']}.*",
31+
)
32+
bin["release"] = FileArtifact(
33+
proj=self.proj,
34+
cmd=["cargo", "build", "--release"],
35+
# extension is platform specific
36+
fn=f"{self.proj.url}/target/release/{meta['package']['name']}.*",
37+
)
38+
self.artifacts["file"] = bin
39+
40+
@staticmethod
41+
def _create(path: str) -> None:
42+
subprocess.check_call(["cargo", "init"], cwd=path)
2143

2244

2345
class RustPython(Rust, PythonLibrary):
@@ -33,10 +55,18 @@ def match(self) -> bool:
3355
# have a python package directory with the same name as the rust library.
3456

3557
# You can also have metadata.maturin in the Cargo.toml
36-
return (
37-
Rust.match(self)
38-
and "maturin" in self.proj.pyproject.get("tool", {})
39-
and self.proj.pyproject.get("build-backend", "") == "maturin"
58+
return Rust.match(self) and (
59+
"maturin" in self.proj.pyproject.get("tool", {})
60+
or self.proj.pyproject.get("build-system", {}).get("build-backend", "")
61+
== "maturin"
4062
)
4163

42-
# this builds a python-installable wheel in addition to rust artifacts.
64+
def parse(self):
65+
super().parse()
66+
Rust.parse(self)
67+
68+
@staticmethod
69+
def _create(path: str) -> None:
70+
# will fail for existing python libraries, since it doesn't want to edit
71+
# the pyproject.toml build backend.
72+
subprocess.check_call(["maturin", "init", "-b", "pyo3", "--mixed"], cwd=path)

tests/test_roundtrips.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os.path
12
import pytest
23

34
import projspec.proj
@@ -24,12 +25,16 @@
2425
"HuggingFaceRepo",
2526
"uv_script",
2627
"MLFlow",
28+
"Rust",
29+
"RustPython",
2730
],
2831
)
2932
def test_compliant(tmpdir, cls_name):
3033
path = str(tmpdir)
3134
cls = get_cls(cls_name)
32-
proj = cls.create(path)
35+
proj = projspec.Project(path)
36+
files = proj.create(cls_name)
37+
assert os.path.exists(files[0])
3338
if not issubclass(cls, projspec.proj.ProjectExtra):
3439
assert cls_name in proj
3540
else:

tests/test_webapp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
def test_webapp_kwargs(tmpdir):
55
proj = projspec.Project(str(tmpdir))
6-
proj2 = proj.create("flask")
7-
art = proj2.flask.artifacts["server"]["flask-app"]
6+
proj.create("flask")
7+
art = proj.flask.artifacts["server"]["flask-app"]
88
art.make()
99
# defaults for flask
1010
assert art._port == 5000

0 commit comments

Comments
 (0)