Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ dependencies = [
]

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

[project.scripts]
Expand Down
2 changes: 1 addition & 1 deletion src/projspec/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def make(artifact, path, storage_options, types, xtypes):
proj = projspec.Project(
path, storage_options=storage_options, types=types, xtypes=xtypes
)
proj.make(artifact)
print("Created:", proj.make(artifact))


@main.command()
Expand Down
31 changes: 23 additions & 8 deletions src/projspec/proj/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,16 @@ def __init__(
self.artifacts = AttrDict()
# read and respect .gitignore? for exclude directories?
self.excludes = excludes or default_excludes
self._pyproject = None
self._scanned_files = None
self._reset()
self.resolve(walk=walk, types=types, xtypes=xtypes)

def _reset(self):
"""Prepare this project for parsing with new specs"""
# cached properties
self.__dict__.pop("basenames", None)
self.__dict__.pop("filelist", None)
self.__dict__.pop("pyproject", None)
self._scanned_files = None
# clear cached files
self._scanned_files = None

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

def create(self, name: str):
"""Make this project conform to the given project spec type."""
def create(self, name: str) -> list[str]:
"""Make this project conform to the given project spec type.

Returns a list of files that were created.
"""
cls = get_cls(name)
# causes reparse and makes a new instance
# could rerun resolve or only parse for give type and add, instead.
return cls.create(self.path)
allfiles = self.fs.find(self.url, detail=False)
cls.create(self.url)
allfiles2 = self.fs.find(self.url, detasil=False)
self._reset()
spec = cls(self)
spec.parse()
self.specs[camel_to_snake(cls.__name__)] = spec
return sorted(set(allfiles2) - set(allfiles))

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

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

def parse(self) -> None:
raise ParseFailed
Expand Down
40 changes: 35 additions & 5 deletions src/projspec/proj/rust.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import subprocess

import toml
from projspec.proj import ProjectSpec, PythonLibrary
from projspec.utils import AttrDict


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

def parse(self):
from projspec.content.metadata import DescriptiveMetadata
from projspec.artifact.base import FileArtifact

with self.proj.fs.open(f"{self.proj.url}/Cargo.toml", "rt") as f:
meta = toml.load(f)
self.contents["desciptive_metadata"] = DescriptiveMetadata(
proj=self.proj, meta=meta.get("package")
)
bin = AttrDict()
bin["debug"] = FileArtifact(
proj=self.proj,
cmd=["cargo", "build"],
# extension is platform specific
fn=f"{self.proj.url}/target/debug/{meta['package']['name']}.*",
)
bin["release"] = FileArtifact(
proj=self.proj,
cmd=["cargo", "build", "--release"],
# extension is platform specific
fn=f"{self.proj.url}/target/release/{meta['package']['name']}.*",
)
self.artifacts["file"] = bin

@staticmethod
def _create(path: str) -> None:
subprocess.check_call(["cargo", "init"], cwd=path)


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

# You can also have metadata.maturin in the Cargo.toml
return (
Rust.match(self)
and "maturin" in self.proj.pyproject.get("tool", {})
and self.proj.pyproject.get("build-backend", "") == "maturin"
return Rust.match(self) and (
"maturin" in self.proj.pyproject.get("tool", {})
or self.proj.pyproject.get("build-system", {}).get("build-backend", "")
== "maturin"
)

# this builds a python-installable wheel in addition to rust artifacts.
def parse(self):
super().parse()
Rust.parse(self)

@staticmethod
def _create(path: str) -> None:
# will fail for existing python libraries, since it doesn't want to edit
# the pyproject.toml build backend.
subprocess.check_call(["maturin", "init", "-b", "pyo3", "--mixed"], cwd=path)
7 changes: 6 additions & 1 deletion tests/test_roundtrips.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os.path
import pytest

import projspec.proj
Expand All @@ -24,12 +25,16 @@
"HuggingFaceRepo",
"uv_script",
"MLFlow",
"Rust",
"RustPython",
],
)
def test_compliant(tmpdir, cls_name):
path = str(tmpdir)
cls = get_cls(cls_name)
proj = cls.create(path)
proj = projspec.Project(path)
files = proj.create(cls_name)
assert os.path.exists(files[0])
if not issubclass(cls, projspec.proj.ProjectExtra):
assert cls_name in proj
else:
Expand Down
4 changes: 2 additions & 2 deletions tests/test_webapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

def test_webapp_kwargs(tmpdir):
proj = projspec.Project(str(tmpdir))
proj2 = proj.create("flask")
art = proj2.flask.artifacts["server"]["flask-app"]
proj.create("flask")
art = proj.flask.artifacts["server"]["flask-app"]
art.make()
# defaults for flask
assert art._port == 5000
Expand Down