Skip to content

Commit 0e2a5f9

Browse files
authored
feat: add RunExportsJson (#566)
1 parent 047e4a4 commit 0e2a5f9

13 files changed

+472
-102
lines changed

py-rattler/Cargo.lock

+117-95
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

py-rattler/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ rattler_solve = { path = "../crates/rattler_solve", default-features = false, fe
3333
] }
3434
rattler_index = { path = "../crates/rattler_index" }
3535
rattler_lock = { path = "../crates/rattler_lock", default-features = false }
36+
rattler_package_streaming = { path = "../crates/rattler_package_streaming", default-features = false }
3637

3738
pyo3 = { version = "0.19", features = [
3839
"abi3-py38",

py-rattler/docs/about_json.md

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
# AboutJson
22

33
::: rattler.package.about_json
4-

py-rattler/docs/run_exports_json.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
# RunExportsJson
3+
4+
::: rattler.package.run_exports_json

py-rattler/mkdocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ nav:
4848
- PypiPackageEnvironmentData: pypi_package_environment_data.md
4949
- metadata:
5050
- AboutJson: about_json.md
51+
- RunExportsJson: run_exports_json.md
5152
- match_spec:
5253
- MatchSpec: match_spec.md
5354
- NamelessMatchSpec: nameless_match_spec.md

py-rattler/rattler/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from rattler.channel import Channel, ChannelConfig
1111
from rattler.networking import AuthenticatedClient, fetch_repo_data
1212
from rattler.virtual_package import GenericVirtualPackage, VirtualPackage
13-
from rattler.package import PackageName, AboutJson
13+
from rattler.package import PackageName, AboutJson, RunExportsJson
1414
from rattler.prefix import PrefixRecord, PrefixPaths
1515
from rattler.solver import solve
1616
from rattler.platform import Platform
@@ -61,4 +61,5 @@
6161
"link",
6262
"index",
6363
"AboutJson",
64+
"RunExportsJson",
6465
]

py-rattler/rattler/exceptions.py

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
ConvertSubdirError,
1919
VersionBumpError,
2020
EnvironmentCreationError,
21+
ExtractError,
2122
)
2223
except ImportError:
2324
# They are only redefined for documentation purposes
@@ -77,6 +78,9 @@ class VersionBumpError(Exception): # type: ignore[no-redef]
7778
class EnvironmentCreationError(Exception): # type: ignore[no-redef]
7879
"""An error that can occur when creating an environment."""
7980

81+
class ExtractError(Exception): # type: ignore[no-redef]
82+
"""An error that can occur when extracting an archive."""
83+
8084

8185
__all__ = [
8286
"ActivationError",
@@ -97,4 +101,5 @@ class EnvironmentCreationError(Exception): # type: ignore[no-redef]
97101
"ConvertSubdirError",
98102
"VersionBumpError",
99103
"EnvironmentCreationError",
104+
"ExtractError",
100105
]
+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from rattler.package.package_name import PackageName
22
from rattler.package.about_json import AboutJson
3+
from rattler.package.run_exports_json import RunExportsJson
34

4-
__all__ = ["PackageName", "AboutJson"]
5+
__all__ = ["PackageName", "AboutJson", "RunExportsJson"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
from __future__ import annotations
2+
import os
3+
from pathlib import Path
4+
from typing import List
5+
6+
from rattler.rattler import PyRunExportsJson
7+
8+
9+
class RunExportsJson:
10+
"""
11+
A representation of the `run_exports.json` file found in package archives.
12+
The `run_exports.json` file contains information about the run exports of a package
13+
"""
14+
15+
_inner: PyRunExportsJson
16+
17+
@staticmethod
18+
def from_package_archive(path: os.PathLike[str]) -> RunExportsJson:
19+
"""
20+
Parses the package file from archive.
21+
Note: If you want to extract multiple `info/*` files then this will be slightly
22+
slower than manually iterating over the archive entries with
23+
custom logic as this skips over the rest of the archive
24+
25+
Examples
26+
--------
27+
```python
28+
>>> run_exports = RunExportsJson.from_package_archive(
29+
... "../test-data/with-symlinks/python-3.10.6-h2c4edbf_0_cpython.tar.bz2"
30+
... )
31+
>>> run_exports
32+
RunExportsJson()
33+
>>>
34+
```
35+
"""
36+
return RunExportsJson._from_py_run_exports_json(
37+
PyRunExportsJson.from_package_archive(path)
38+
)
39+
40+
@staticmethod
41+
def from_path(path: os.PathLike[str]) -> RunExportsJson:
42+
"""
43+
Parses the object from a file specified by a `path`, using a format
44+
appropriate for the file type.
45+
46+
For example, if the file is in JSON format, this function reads the data
47+
from the file at the specified path, parse the JSON string and return the
48+
resulting object. If the file is not in a parsable format or if the file
49+
could not read, this function returns an error.
50+
"""
51+
return RunExportsJson._from_py_run_exports_json(
52+
PyRunExportsJson.from_path(Path(path))
53+
)
54+
55+
@staticmethod
56+
def from_package_directory(path: os.PathLike[str]) -> RunExportsJson:
57+
"""
58+
Parses the object by looking up the appropriate file from the root of the
59+
specified Conda archive directory, using a format appropriate for the file
60+
type.
61+
62+
For example, if the file is in JSON format, this function reads the
63+
appropriate file from the archive, parse the JSON string and return the
64+
resulting object. If the file is not in a parsable format or if the file
65+
could not be read, this function returns an error.
66+
"""
67+
return RunExportsJson._from_py_run_exports_json(
68+
PyRunExportsJson.from_package_directory(Path(path))
69+
)
70+
71+
@staticmethod
72+
def from_str(string: str) -> RunExportsJson:
73+
"""
74+
Parses the object from a string, using a format appropriate for the file
75+
type.
76+
77+
For example, if the file is in JSON format, this function parses the JSON
78+
string and returns the resulting object. If the file is not in a parsable
79+
format, this function returns an error.
80+
"""
81+
return RunExportsJson._from_py_run_exports_json(
82+
PyRunExportsJson.from_str(string)
83+
)
84+
85+
@staticmethod
86+
def package_path() -> str:
87+
"""
88+
Returns the path to the file within the Conda archive.
89+
90+
The path is relative to the root of the archive and includes any necessary
91+
directories.
92+
"""
93+
return PyRunExportsJson.package_path()
94+
95+
@property
96+
def weak(self) -> List[str]:
97+
"""
98+
Weak run exports apply a dependency from host to run.
99+
100+
Examples
101+
--------
102+
```python
103+
>>> run_exports = RunExportsJson.from_package_archive(
104+
... "../test-data/with-symlinks/python-3.10.6-h2c4edbf_0_cpython.tar.bz2"
105+
... )
106+
>>> run_exports.weak
107+
['python_abi 3.10.* *_cp310']
108+
>>>
109+
```
110+
"""
111+
return self._inner.weak
112+
113+
@property
114+
def strong(self) -> List[str]:
115+
"""
116+
Strong run exports apply a dependency from build to host and run.
117+
118+
Examples
119+
--------
120+
```python
121+
>>> run_exports = RunExportsJson.from_package_archive(
122+
... "../test-data/with-symlinks/python-3.10.6-h2c4edbf_0_cpython.tar.bz2"
123+
... )
124+
>>> run_exports.strong
125+
[]
126+
>>>
127+
```
128+
"""
129+
return self._inner.strong
130+
131+
@property
132+
def noarch(self) -> List[str]:
133+
"""
134+
NoArch run exports apply a run export only to noarch packages (other run exports are ignored).
135+
For example, python uses this to apply a dependency on python to all noarch packages, but not to
136+
the python_abi package.
137+
138+
Examples
139+
--------
140+
```python
141+
>>> run_exports = RunExportsJson.from_package_archive(
142+
... "../test-data/with-symlinks/python-3.10.6-h2c4edbf_0_cpython.tar.bz2"
143+
... )
144+
>>> run_exports.noarch
145+
['python']
146+
>>>
147+
```
148+
"""
149+
return self._inner.noarch
150+
151+
@property
152+
def weak_constrains(self) -> List[str]:
153+
"""
154+
Weak constrains apply a constrain dependency from host to build, or run to host.
155+
156+
Examples
157+
--------
158+
```python
159+
>>> run_exports = RunExportsJson.from_package_archive(
160+
... "../test-data/with-symlinks/python-3.10.6-h2c4edbf_0_cpython.tar.bz2"
161+
... )
162+
>>> run_exports.weak_constrains
163+
[]
164+
>>>
165+
```
166+
"""
167+
return self._inner.weak_constrains
168+
169+
@property
170+
def strong_constrains(self) -> List[str]:
171+
"""
172+
Strong constrains apply a constrain dependency from build to host and run.
173+
174+
Examples
175+
--------
176+
```python
177+
>>> run_exports = RunExportsJson.from_package_archive(
178+
... "../test-data/with-symlinks/python-3.10.6-h2c4edbf_0_cpython.tar.bz2"
179+
... )
180+
>>> run_exports.strong_constrains
181+
[]
182+
>>>
183+
```
184+
"""
185+
return self._inner.strong_constrains
186+
187+
@classmethod
188+
def _from_py_run_exports_json(
189+
cls, py_run_exports_json: PyRunExportsJson
190+
) -> RunExportsJson:
191+
run_exports_json = cls.__new__(cls)
192+
run_exports_json._inner = py_run_exports_json
193+
194+
return run_exports_json
195+
196+
def __repr__(self) -> str:
197+
"""
198+
Returns a representation of the RunExportsJson.
199+
"""
200+
return "RunExportsJson()"

py-rattler/src/error.rs

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use rattler_conda_types::{
88
ParseMatchSpecError, ParsePlatformError, ParseVersionError, VersionBumpError,
99
};
1010
use rattler_lock::{ConversionError, ParseCondaLockError};
11+
use rattler_package_streaming::ExtractError;
1112
use rattler_repodata_gateway::fetch::FetchRepoDataError;
1213
use rattler_shell::activation::ActivationError;
1314
use rattler_solve::SolveError;
@@ -59,6 +60,8 @@ pub enum PyRattlerError {
5960
RequirementError(String),
6061
#[error("{0}")]
6162
EnvironmentCreationError(String),
63+
#[error(transparent)]
64+
ExtractError(#[from] ExtractError),
6265
}
6366

6467
impl From<PyRattlerError> for PyErr {
@@ -105,6 +108,7 @@ impl From<PyRattlerError> for PyErr {
105108
PyRattlerError::EnvironmentCreationError(err) => {
106109
EnvironmentCreationException::new_err(err)
107110
}
111+
PyRattlerError::ExtractError(err) => ExtractException::new_err(err.to_string()),
108112
}
109113
}
110114
}
@@ -130,3 +134,4 @@ create_exception!(exceptions, ParseCondaLockException, PyException);
130134
create_exception!(exceptions, ConversionException, PyException);
131135
create_exception!(exceptions, RequirementException, PyException);
132136
create_exception!(exceptions, EnvironmentCreationException, PyException);
137+
create_exception!(exceptions, ExtractException, PyException);

py-rattler/src/lib.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ mod solver;
1919
mod version;
2020
mod virtual_package;
2121

22+
mod run_exports_json;
2223
use about_json::PyAboutJson;
2324
use channel::{PyChannel, PyChannelConfig};
2425
use error::{
2526
ActivationException, CacheDirException, ConvertSubdirException, DetectVirtualPackageException,
26-
EnvironmentCreationException, FetchRepoDataException, InvalidChannelException,
27-
InvalidMatchSpecException, InvalidPackageNameException, InvalidUrlException,
28-
InvalidVersionException, IoException, LinkException, ParseArchException,
27+
EnvironmentCreationException, ExtractException, FetchRepoDataException,
28+
InvalidChannelException, InvalidMatchSpecException, InvalidPackageNameException,
29+
InvalidUrlException, InvalidVersionException, IoException, LinkException, ParseArchException,
2930
ParsePlatformException, PyRattlerError, SolverException, TransactionException,
3031
VersionBumpException,
3132
};
@@ -40,6 +41,7 @@ use networking::{authenticated_client::PyAuthenticatedClient, py_fetch_repo_data
4041
use package_name::PyPackageName;
4142
use prefix_paths::PyPrefixPaths;
4243
use repo_data::{patch_instructions::PyPatchInstructions, sparse::PySparseRepoData, PyRepoData};
44+
use run_exports_json::PyRunExportsJson;
4345
use version::PyVersion;
4446

4547
use pyo3::prelude::*;
@@ -97,6 +99,8 @@ fn rattler(py: Python<'_>, m: &PyModule) -> PyResult<()> {
9799

98100
m.add_class::<PyAboutJson>().unwrap();
99101

102+
m.add_class::<PyRunExportsJson>().unwrap();
103+
100104
m.add_function(wrap_pyfunction!(py_solve, m).unwrap())
101105
.unwrap();
102106
m.add_function(wrap_pyfunction!(get_rattler_version, m).unwrap())
@@ -170,5 +174,8 @@ fn rattler(py: Python<'_>, m: &PyModule) -> PyResult<()> {
170174
)
171175
.unwrap();
172176

177+
m.add("ExtractError", py.get_type::<ExtractException>())
178+
.unwrap();
179+
173180
Ok(())
174181
}

py-rattler/src/lock/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,11 @@ impl PyPypiPackageEnvironmentData {
476476
/// The extras enabled for the package. Note that the order doesn't matter.
477477
#[getter]
478478
pub fn extras(&self) -> BTreeSet<String> {
479-
self.inner.extras.iter().map(|e| e.to_string()).collect()
479+
self.inner
480+
.extras
481+
.iter()
482+
.map(std::string::ToString::to_string)
483+
.collect()
480484
}
481485
}
482486

0 commit comments

Comments
 (0)