python_packed_resources/
resource.rs

1// Copyright 2022 Gregory Szorc.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use std::{borrow::Cow, collections::HashMap, path::Path};
10
11/// Represents an indexed resource.
12///
13/// The resource has a name and type affinity via various `is_*` fields.
14///
15/// The data for the resource may be present in the instance or referenced
16/// via an external filesystem path.
17///
18/// Data fields are `Cow<T>` and can either hold a borrowed reference or
19/// owned data. This allows the use of a single type to both hold
20/// data or reference it from some other location.
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct Resource<'a, X: 'a>
23where
24    [X]: ToOwned<Owned = Vec<X>>,
25{
26    /// The resource name.
27    pub name: Cow<'a, str>,
28
29    /// Whether this resource defines a Python module/package.
30    pub is_python_module: bool,
31
32    /// Whether this resource defines a builtin extension module.
33    pub is_python_builtin_extension_module: bool,
34
35    /// Whether this resource defines a frozen Python module.
36    pub is_python_frozen_module: bool,
37
38    /// Whether this resource defines a Python extension module.
39    pub is_python_extension_module: bool,
40
41    /// Whether this resource defines a shared library.
42    pub is_shared_library: bool,
43
44    /// Whether this resource defines data for an arbitrary file.
45    ///
46    /// If set, `name` is the UTF-8 encoded filename being represented.
47    ///
48    /// The file data should exist in one of the `file_data_*` fields.
49    pub is_utf8_filename_data: bool,
50
51    /// Whether the Python module is a package.
52    pub is_python_package: bool,
53
54    /// Whether the Python module is a namespace package.
55    pub is_python_namespace_package: bool,
56
57    /// Python module source code to use to import module from memory.
58    pub in_memory_source: Option<Cow<'a, [X]>>,
59
60    /// Python module bytecode to use to import module from memory.
61    pub in_memory_bytecode: Option<Cow<'a, [X]>>,
62
63    /// Python module bytecode at optimized level 1 to use to import from memory.
64    pub in_memory_bytecode_opt1: Option<Cow<'a, [X]>>,
65
66    /// Python module bytecode at optimized level 2 to use to import from memory.
67    pub in_memory_bytecode_opt2: Option<Cow<'a, [X]>>,
68
69    /// Native machine code constituting a shared library for an extension module
70    /// which can be imported from memory. (Not supported on all platforms.)
71    pub in_memory_extension_module_shared_library: Option<Cow<'a, [X]>>,
72
73    /// Mapping of virtual filename to data for resources to expose to Python's
74    /// `importlib.resources` API via in-memory data access.
75    pub in_memory_package_resources: Option<HashMap<Cow<'a, str>, Cow<'a, [X]>>>,
76
77    /// Mapping of virtual filename to data for package distribution metadata
78    /// to expose to Python's `importlib.metadata` API via in-memory data access.
79    pub in_memory_distribution_resources: Option<HashMap<Cow<'a, str>, Cow<'a, [X]>>>,
80
81    /// Native machine code constituting a shared library which can be imported from memory.
82    ///
83    /// In-memory loading of shared libraries is not supported on all platforms.
84    pub in_memory_shared_library: Option<Cow<'a, [X]>>,
85
86    /// Sequence of names of shared libraries this resource depends on.
87    pub shared_library_dependency_names: Option<Vec<Cow<'a, str>>>,
88
89    /// Relative path to file containing Python module source code.
90    pub relative_path_module_source: Option<Cow<'a, Path>>,
91
92    /// Relative path to file containing Python module bytecode.
93    pub relative_path_module_bytecode: Option<Cow<'a, Path>>,
94
95    /// Relative path to file containing Python module bytecode at optimization level 1.
96    pub relative_path_module_bytecode_opt1: Option<Cow<'a, Path>>,
97
98    /// Relative path to file containing Python module bytecode at optimization level 2.
99    pub relative_path_module_bytecode_opt2: Option<Cow<'a, Path>>,
100
101    /// Relative path to file containing Python extension module loadable as a shared library.
102    pub relative_path_extension_module_shared_library: Option<Cow<'a, Path>>,
103
104    /// Mapping of Python package resource names to relative filesystem paths for those resources.
105    pub relative_path_package_resources: Option<HashMap<Cow<'a, str>, Cow<'a, Path>>>,
106
107    /// Mapping of Python package distribution files to relative filesystem paths for those resources.
108    pub relative_path_distribution_resources: Option<HashMap<Cow<'a, str>, Cow<'a, Path>>>,
109
110    /// Whether this resource's file data should be executable.
111    pub file_executable: bool,
112
113    /// Holds arbitrary file data in memory.
114    pub file_data_embedded: Option<Cow<'a, [X]>>,
115
116    /// Holds arbitrary file data in a relative path encoded in UTF-8.
117    pub file_data_utf8_relative_path: Option<Cow<'a, str>>,
118}
119
120impl<'a, X> Default for Resource<'a, X>
121where
122    [X]: ToOwned<Owned = Vec<X>>,
123{
124    fn default() -> Self {
125        Resource {
126            name: Cow::Borrowed(""),
127            is_python_module: false,
128            is_python_builtin_extension_module: false,
129            is_python_frozen_module: false,
130            is_python_extension_module: false,
131            is_shared_library: false,
132            is_utf8_filename_data: false,
133            is_python_package: false,
134            is_python_namespace_package: false,
135            in_memory_source: None,
136            in_memory_bytecode: None,
137            in_memory_bytecode_opt1: None,
138            in_memory_bytecode_opt2: None,
139            in_memory_extension_module_shared_library: None,
140            in_memory_package_resources: None,
141            in_memory_distribution_resources: None,
142            in_memory_shared_library: None,
143            shared_library_dependency_names: None,
144            relative_path_module_source: None,
145            relative_path_module_bytecode: None,
146            relative_path_module_bytecode_opt1: None,
147            relative_path_module_bytecode_opt2: None,
148            relative_path_extension_module_shared_library: None,
149            relative_path_package_resources: None,
150            relative_path_distribution_resources: None,
151            file_executable: false,
152            file_data_embedded: None,
153            file_data_utf8_relative_path: None,
154        }
155    }
156}
157
158impl<'a, X> AsRef<Resource<'a, X>> for Resource<'a, X>
159where
160    [X]: ToOwned<Owned = Vec<X>>,
161{
162    fn as_ref(&self) -> &Resource<'a, X> {
163        self
164    }
165}
166
167impl<'a, X> Resource<'a, X>
168where
169    [X]: ToOwned<Owned = Vec<X>>,
170{
171    /// Merge another resource into this one.
172    ///
173    /// Fields from other will overwrite fields from self.
174    pub fn merge_from(&mut self, other: Resource<'a, X>) -> Result<(), &'static str> {
175        if self.name != other.name {
176            return Err("resource names must be identical to perform a merge");
177        }
178
179        self.is_python_module |= other.is_python_module;
180        self.is_python_builtin_extension_module |= other.is_python_builtin_extension_module;
181        self.is_python_frozen_module |= other.is_python_frozen_module;
182        self.is_python_extension_module |= other.is_python_extension_module;
183        self.is_shared_library |= other.is_shared_library;
184        self.is_utf8_filename_data |= other.is_utf8_filename_data;
185        self.is_python_package |= other.is_python_package;
186        self.is_python_namespace_package |= other.is_python_namespace_package;
187        if let Some(value) = other.in_memory_source {
188            self.in_memory_source.replace(value);
189        }
190        if let Some(value) = other.in_memory_bytecode {
191            self.in_memory_bytecode.replace(value);
192        }
193        if let Some(value) = other.in_memory_bytecode_opt1 {
194            self.in_memory_bytecode_opt1.replace(value);
195        }
196        if let Some(value) = other.in_memory_bytecode_opt2 {
197            self.in_memory_bytecode_opt2.replace(value);
198        }
199        if let Some(value) = other.in_memory_extension_module_shared_library {
200            self.in_memory_extension_module_shared_library
201                .replace(value);
202        }
203        if let Some(value) = other.in_memory_package_resources {
204            self.in_memory_package_resources.replace(value);
205        }
206        if let Some(value) = other.in_memory_distribution_resources {
207            self.in_memory_distribution_resources.replace(value);
208        }
209        if let Some(value) = other.in_memory_shared_library {
210            self.in_memory_shared_library.replace(value);
211        }
212        if let Some(value) = other.shared_library_dependency_names {
213            self.shared_library_dependency_names.replace(value);
214        }
215        if let Some(value) = other.relative_path_module_source {
216            self.relative_path_module_source.replace(value);
217        }
218        if let Some(value) = other.relative_path_module_bytecode {
219            self.relative_path_module_bytecode.replace(value);
220        }
221        if let Some(value) = other.relative_path_module_bytecode_opt1 {
222            self.relative_path_module_bytecode_opt1.replace(value);
223        }
224        if let Some(value) = other.relative_path_module_bytecode_opt2 {
225            self.relative_path_module_bytecode_opt2.replace(value);
226        }
227        if let Some(value) = other.relative_path_extension_module_shared_library {
228            self.relative_path_extension_module_shared_library
229                .replace(value);
230        }
231        if let Some(value) = other.relative_path_package_resources {
232            self.relative_path_package_resources.replace(value);
233        }
234        if let Some(value) = other.relative_path_distribution_resources {
235            self.relative_path_distribution_resources.replace(value);
236        }
237        // TODO we should probably store an Option<bool> here so this assignment is
238        // unambiguous.
239        self.file_executable |= other.file_executable;
240        if let Some(value) = other.file_data_embedded {
241            self.file_data_embedded.replace(value);
242        }
243        if let Some(value) = other.file_data_utf8_relative_path {
244            self.file_data_utf8_relative_path.replace(value);
245        }
246
247        Ok(())
248    }
249
250    pub fn to_owned(&self) -> Resource<'static, X> {
251        Resource {
252            name: Cow::Owned(self.name.clone().into_owned()),
253            is_python_module: self.is_python_module,
254            is_python_builtin_extension_module: self.is_python_builtin_extension_module,
255            is_python_frozen_module: self.is_python_frozen_module,
256            is_python_extension_module: self.is_python_extension_module,
257            is_shared_library: self.is_shared_library,
258            is_utf8_filename_data: self.is_utf8_filename_data,
259            is_python_package: self.is_python_package,
260            is_python_namespace_package: self.is_python_namespace_package,
261            in_memory_source: self
262                .in_memory_source
263                .as_ref()
264                .map(|value| Cow::Owned(value.clone().into_owned())),
265            in_memory_bytecode: self
266                .in_memory_bytecode
267                .as_ref()
268                .map(|value| Cow::Owned(value.clone().into_owned())),
269            in_memory_bytecode_opt1: self
270                .in_memory_bytecode_opt1
271                .as_ref()
272                .map(|value| Cow::Owned(value.clone().into_owned())),
273            in_memory_bytecode_opt2: self
274                .in_memory_bytecode_opt2
275                .as_ref()
276                .map(|value| Cow::Owned(value.clone().into_owned())),
277            in_memory_extension_module_shared_library: self
278                .in_memory_extension_module_shared_library
279                .as_ref()
280                .map(|value| Cow::Owned(value.clone().into_owned())),
281            in_memory_package_resources: self.in_memory_package_resources.as_ref().map(|value| {
282                value
283                    .iter()
284                    .map(|(k, v)| {
285                        (
286                            Cow::Owned(k.clone().into_owned()),
287                            Cow::Owned(v.clone().into_owned()),
288                        )
289                    })
290                    .collect()
291            }),
292            in_memory_distribution_resources: self.in_memory_distribution_resources.as_ref().map(
293                |value| {
294                    value
295                        .iter()
296                        .map(|(k, v)| {
297                            (
298                                Cow::Owned(k.clone().into_owned()),
299                                Cow::Owned(v.clone().into_owned()),
300                            )
301                        })
302                        .collect()
303                },
304            ),
305            in_memory_shared_library: self
306                .in_memory_source
307                .as_ref()
308                .map(|value| Cow::Owned(value.clone().into_owned())),
309            shared_library_dependency_names: self.shared_library_dependency_names.as_ref().map(
310                |value| {
311                    value
312                        .iter()
313                        .map(|x| Cow::Owned(x.clone().into_owned()))
314                        .collect()
315                },
316            ),
317            relative_path_module_source: self
318                .relative_path_module_source
319                .as_ref()
320                .map(|value| Cow::Owned(value.clone().into_owned())),
321            relative_path_module_bytecode: self
322                .relative_path_module_bytecode
323                .as_ref()
324                .map(|value| Cow::Owned(value.clone().into_owned())),
325            relative_path_module_bytecode_opt1: self
326                .relative_path_module_bytecode_opt1
327                .as_ref()
328                .map(|value| Cow::Owned(value.clone().into_owned())),
329            relative_path_module_bytecode_opt2: self
330                .relative_path_module_bytecode_opt2
331                .as_ref()
332                .map(|value| Cow::Owned(value.clone().into_owned())),
333            relative_path_extension_module_shared_library: self
334                .relative_path_extension_module_shared_library
335                .as_ref()
336                .map(|value| Cow::Owned(value.clone().into_owned())),
337            relative_path_package_resources: self.relative_path_package_resources.as_ref().map(
338                |value| {
339                    value
340                        .iter()
341                        .map(|(k, v)| {
342                            (
343                                Cow::Owned(k.clone().into_owned()),
344                                Cow::Owned(v.clone().into_owned()),
345                            )
346                        })
347                        .collect()
348                },
349            ),
350            relative_path_distribution_resources: self
351                .relative_path_distribution_resources
352                .as_ref()
353                .map(|value| {
354                    value
355                        .iter()
356                        .map(|(k, v)| {
357                            (
358                                Cow::Owned(k.clone().into_owned()),
359                                Cow::Owned(v.clone().into_owned()),
360                            )
361                        })
362                        .collect()
363                }),
364            file_executable: self.file_executable,
365            file_data_embedded: self
366                .file_data_embedded
367                .as_ref()
368                .map(|value| Cow::Owned(value.clone().into_owned())),
369            file_data_utf8_relative_path: self
370                .file_data_utf8_relative_path
371                .as_ref()
372                .map(|value| Cow::Owned(value.clone().into_owned())),
373        }
374    }
375}