1use {
12 crate::{
13 bytecode::{CompileMode, PythonBytecodeCompiler},
14 licensing::LicensedComponent,
15 module_util::{is_package_from_path, packages_from_module_name, resolve_path_for_module},
16 python_source::has_dunder_file,
17 },
18 anyhow::{anyhow, Result},
19 simple_file_manifest::{File, FileData},
20 std::{
21 borrow::Cow,
22 collections::HashMap,
23 hash::BuildHasher,
24 path::{Path, PathBuf},
25 },
26};
27
28#[cfg(feature = "serialization")]
29use serde::{Deserialize, Serialize};
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35#[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))]
36pub enum BytecodeOptimizationLevel {
37 #[cfg_attr(feature = "serialization", serde(rename = "0"))]
41 Zero,
42
43 #[cfg_attr(feature = "serialization", serde(rename = "1"))]
47 One,
48
49 #[cfg_attr(feature = "serialization", serde(rename = "2"))]
53 Two,
54}
55
56impl BytecodeOptimizationLevel {
57 pub fn to_extra_tag(&self) -> &'static str {
59 match self {
60 BytecodeOptimizationLevel::Zero => "",
61 BytecodeOptimizationLevel::One => ".opt-1",
62 BytecodeOptimizationLevel::Two => ".opt-2",
63 }
64 }
65}
66
67impl TryFrom<i32> for BytecodeOptimizationLevel {
68 type Error = &'static str;
69
70 fn try_from(i: i32) -> Result<Self, Self::Error> {
71 match i {
72 0 => Ok(BytecodeOptimizationLevel::Zero),
73 1 => Ok(BytecodeOptimizationLevel::One),
74 2 => Ok(BytecodeOptimizationLevel::Two),
75 _ => Err("unsupported bytecode optimization level"),
76 }
77 }
78}
79
80impl From<BytecodeOptimizationLevel> for i32 {
81 fn from(level: BytecodeOptimizationLevel) -> Self {
82 match level {
83 BytecodeOptimizationLevel::Zero => 0,
84 BytecodeOptimizationLevel::One => 1,
85 BytecodeOptimizationLevel::Two => 2,
86 }
87 }
88}
89
90#[derive(Clone, Debug, PartialEq)]
92pub struct PythonModuleSource {
93 pub name: String,
95 pub source: FileData,
97 pub is_package: bool,
99 pub cache_tag: String,
103 pub is_stdlib: bool,
107 pub is_test: bool,
112}
113
114impl PythonModuleSource {
115 pub fn description(&self) -> String {
116 format!("source code for Python module {}", self.name)
117 }
118
119 pub fn to_memory(&self) -> Result<Self> {
120 Ok(Self {
121 name: self.name.clone(),
122 source: self.source.to_memory()?,
123 is_package: self.is_package,
124 cache_tag: self.cache_tag.clone(),
125 is_stdlib: self.is_stdlib,
126 is_test: self.is_test,
127 })
128 }
129
130 pub fn package(&self) -> String {
134 if self.is_package {
135 self.name.clone()
136 } else if let Some(idx) = self.name.rfind('.') {
137 self.name[0..idx].to_string()
138 } else {
139 self.name.clone()
140 }
141 }
142
143 pub fn top_level_package(&self) -> &str {
145 if let Some(idx) = self.name.find('.') {
146 &self.name[0..idx]
147 } else {
148 &self.name
149 }
150 }
151
152 pub fn as_bytecode_module(
154 &self,
155 optimize_level: BytecodeOptimizationLevel,
156 ) -> PythonModuleBytecodeFromSource {
157 PythonModuleBytecodeFromSource {
158 name: self.name.clone(),
159 source: self.source.clone(),
160 optimize_level,
161 is_package: self.is_package,
162 cache_tag: self.cache_tag.clone(),
163 is_stdlib: self.is_stdlib,
164 is_test: self.is_test,
165 }
166 }
167
168 pub fn resolve_path(&self, prefix: &str) -> PathBuf {
170 resolve_path_for_module(prefix, &self.name, self.is_package, None)
171 }
172
173 pub fn has_dunder_file(&self) -> Result<bool> {
175 has_dunder_file(&self.source.resolve_content()?)
176 }
177}
178
179#[derive(Clone, Debug, PartialEq)]
184pub struct PythonModuleBytecodeFromSource {
185 pub name: String,
186 pub source: FileData,
187 pub optimize_level: BytecodeOptimizationLevel,
188 pub is_package: bool,
189 pub cache_tag: String,
193 pub is_stdlib: bool,
197 pub is_test: bool,
202}
203
204impl PythonModuleBytecodeFromSource {
205 pub fn description(&self) -> String {
206 format!(
207 "bytecode for Python module {} at O{} (compiled from source)",
208 self.name, self.optimize_level as i32
209 )
210 }
211
212 pub fn to_memory(&self) -> Result<Self> {
213 Ok(Self {
214 name: self.name.clone(),
215 source: self.source.to_memory()?,
216 optimize_level: self.optimize_level,
217 is_package: self.is_package,
218 cache_tag: self.cache_tag.clone(),
219 is_stdlib: self.is_stdlib,
220 is_test: self.is_test,
221 })
222 }
223
224 pub fn compile(
226 &self,
227 compiler: &mut dyn PythonBytecodeCompiler,
228 mode: CompileMode,
229 ) -> Result<Vec<u8>> {
230 compiler.compile(
231 &self.source.resolve_content()?,
232 &self.name,
233 self.optimize_level,
234 mode,
235 )
236 }
237
238 pub fn resolve_path(&self, prefix: &str) -> PathBuf {
240 let bytecode_tag = match self.optimize_level {
241 BytecodeOptimizationLevel::Zero => self.cache_tag.clone(),
242 BytecodeOptimizationLevel::One => format!("{}.opt-1", self.cache_tag),
243 BytecodeOptimizationLevel::Two => format!("{}.opt-2", self.cache_tag),
244 };
245
246 resolve_path_for_module(prefix, &self.name, self.is_package, Some(&bytecode_tag))
247 }
248
249 pub fn has_dunder_file(&self) -> Result<bool> {
251 has_dunder_file(&self.source.resolve_content()?)
252 }
253}
254
255#[derive(Clone, Debug, PartialEq)]
257pub struct PythonModuleBytecode {
258 pub name: String,
259 bytecode: FileData,
260 pub optimize_level: BytecodeOptimizationLevel,
261 pub is_package: bool,
262 pub cache_tag: String,
266 pub is_stdlib: bool,
270 pub is_test: bool,
275}
276
277impl PythonModuleBytecode {
278 pub fn new(
279 name: &str,
280 optimize_level: BytecodeOptimizationLevel,
281 is_package: bool,
282 cache_tag: &str,
283 data: &[u8],
284 ) -> Self {
285 Self {
286 name: name.to_string(),
287 bytecode: FileData::Memory(data.to_vec()),
288 optimize_level,
289 is_package,
290 cache_tag: cache_tag.to_string(),
291 is_stdlib: false,
292 is_test: false,
293 }
294 }
295
296 pub fn from_path(
297 name: &str,
298 optimize_level: BytecodeOptimizationLevel,
299 cache_tag: &str,
300 path: &Path,
301 ) -> Self {
302 Self {
303 name: name.to_string(),
304 bytecode: FileData::Path(path.to_path_buf()),
305 optimize_level,
306 is_package: is_package_from_path(path),
307 cache_tag: cache_tag.to_string(),
308 is_stdlib: false,
309 is_test: false,
310 }
311 }
312
313 pub fn description(&self) -> String {
314 format!(
315 "bytecode for Python module {} at O{}",
316 self.name, self.optimize_level as i32
317 )
318 }
319
320 pub fn to_memory(&self) -> Result<Self> {
321 Ok(Self {
322 name: self.name.clone(),
323 bytecode: FileData::Memory(self.resolve_bytecode()?),
324 optimize_level: self.optimize_level,
325 is_package: self.is_package,
326 cache_tag: self.cache_tag.clone(),
327 is_stdlib: self.is_stdlib,
328 is_test: self.is_test,
329 })
330 }
331
332 pub fn resolve_bytecode(&self) -> Result<Vec<u8>> {
334 match &self.bytecode {
335 FileData::Memory(data) => Ok(data.clone()),
336 FileData::Path(path) => {
337 let data = std::fs::read(path)?;
338
339 if data.len() >= 16 {
340 Ok(data[16..data.len()].to_vec())
341 } else {
342 Err(anyhow!("bytecode file is too short"))
343 }
344 }
345 }
346 }
347
348 pub fn set_bytecode(&mut self, data: &[u8]) {
350 self.bytecode = FileData::Memory(data.to_vec());
351 }
352
353 pub fn resolve_path(&self, prefix: &str) -> PathBuf {
355 let bytecode_tag = match self.optimize_level {
356 BytecodeOptimizationLevel::Zero => self.cache_tag.clone(),
357 BytecodeOptimizationLevel::One => format!("{}.opt-1", self.cache_tag),
358 BytecodeOptimizationLevel::Two => format!("{}.opt-2", self.cache_tag),
359 };
360
361 resolve_path_for_module(prefix, &self.name, self.is_package, Some(&bytecode_tag))
362 }
363}
364
365#[derive(Clone, Debug, PartialEq)]
367pub struct PythonPackageResource {
368 pub leaf_package: String,
370 pub relative_name: String,
372 pub data: FileData,
374 pub is_stdlib: bool,
378 pub is_test: bool,
380}
381
382impl PythonPackageResource {
383 pub fn description(&self) -> String {
384 format!("Python package resource {}", self.symbolic_name())
385 }
386
387 pub fn to_memory(&self) -> Result<Self> {
388 Ok(Self {
389 leaf_package: self.leaf_package.clone(),
390 relative_name: self.relative_name.clone(),
391 data: self.data.to_memory()?,
392 is_stdlib: self.is_stdlib,
393 is_test: self.is_test,
394 })
395 }
396
397 pub fn symbolic_name(&self) -> String {
398 format!("{}:{}", self.leaf_package, self.relative_name)
399 }
400
401 pub fn resolve_path(&self, prefix: &str) -> PathBuf {
403 let mut path = PathBuf::from(prefix);
404
405 for p in self.leaf_package.split('.') {
406 path = path.join(p);
407 }
408
409 path = path.join(&self.relative_name);
410
411 path
412 }
413}
414
415#[derive(Clone, Debug, PartialEq, Eq)]
417pub enum PythonPackageDistributionResourceFlavor {
418 DistInfo,
420
421 EggInfo,
423}
424
425#[derive(Clone, Debug, PartialEq)]
433pub struct PythonPackageDistributionResource {
434 pub location: PythonPackageDistributionResourceFlavor,
436
437 pub package: String,
439
440 pub version: String,
442
443 pub name: String,
448
449 pub data: FileData,
451}
452
453impl PythonPackageDistributionResource {
454 pub fn description(&self) -> String {
455 format!(
456 "Python package distribution resource {}:{}",
457 self.package, self.name
458 )
459 }
460
461 pub fn to_memory(&self) -> Result<Self> {
462 Ok(Self {
463 location: self.location.clone(),
464 package: self.package.clone(),
465 version: self.version.clone(),
466 name: self.name.clone(),
467 data: self.data.to_memory()?,
468 })
469 }
470
471 pub fn resolve_path(&self, prefix: &str) -> PathBuf {
473 let normalized_package = self.package.to_lowercase().replace('-', "_");
476
477 let p = match self.location {
478 PythonPackageDistributionResourceFlavor::DistInfo => {
479 format!("{}-{}.dist-info", normalized_package, self.version)
480 }
481 PythonPackageDistributionResourceFlavor::EggInfo => {
482 format!("{}-{}.egg-info", normalized_package, self.version)
483 }
484 };
485
486 PathBuf::from(prefix).join(p).join(&self.name)
487 }
488}
489
490#[derive(Clone, Debug, PartialEq)]
495pub struct LibraryDependency {
496 pub name: String,
500
501 pub static_library: Option<FileData>,
503
504 pub static_filename: Option<PathBuf>,
506
507 pub dynamic_library: Option<FileData>,
509
510 pub dynamic_filename: Option<PathBuf>,
512
513 pub framework: bool,
515
516 pub system: bool,
518}
519
520impl LibraryDependency {
521 pub fn to_memory(&self) -> Result<Self> {
522 Ok(Self {
523 name: self.name.clone(),
524 static_library: if let Some(data) = &self.static_library {
525 Some(data.to_memory()?)
526 } else {
527 None
528 },
529 static_filename: self.static_filename.clone(),
530 dynamic_library: if let Some(data) = &self.dynamic_library {
531 Some(data.to_memory()?)
532 } else {
533 None
534 },
535 dynamic_filename: self.dynamic_filename.clone(),
536 framework: self.framework,
537 system: self.system,
538 })
539 }
540}
541
542#[derive(Clone, Debug, PartialEq)]
544pub struct SharedLibrary {
545 pub name: String,
549
550 pub data: FileData,
552
553 pub filename: Option<PathBuf>,
555}
556
557impl TryFrom<&LibraryDependency> for SharedLibrary {
558 type Error = &'static str;
559
560 fn try_from(value: &LibraryDependency) -> Result<Self, Self::Error> {
561 if let Some(data) = &value.dynamic_library {
562 Ok(Self {
563 name: value.name.clone(),
564 data: data.clone(),
565 filename: value.dynamic_filename.clone(),
566 })
567 } else {
568 Err("library dependency does not have a shared library")
569 }
570 }
571}
572
573impl SharedLibrary {
574 pub fn description(&self) -> String {
575 format!("shared library {}", self.name)
576 }
577}
578
579#[derive(Clone, Debug, PartialEq)]
581pub struct PythonExtensionModule {
582 pub name: String,
584 pub init_fn: Option<String>,
586 pub extension_file_suffix: String,
588 pub shared_library: Option<FileData>,
590 pub object_file_data: Vec<FileData>,
593 pub is_package: bool,
595 pub link_libraries: Vec<LibraryDependency>,
597 pub is_stdlib: bool,
601 pub builtin_default: bool,
606 pub required: bool,
608 pub variant: Option<String>,
613 pub license: Option<LicensedComponent>,
615}
616
617impl PythonExtensionModule {
618 pub fn description(&self) -> String {
619 format!("Python extension module {}", self.name)
620 }
621
622 pub fn to_memory(&self) -> Result<Self> {
623 Ok(Self {
624 name: self.name.clone(),
625 init_fn: self.init_fn.clone(),
626 extension_file_suffix: self.extension_file_suffix.clone(),
627 shared_library: if let Some(data) = &self.shared_library {
628 Some(data.to_memory()?)
629 } else {
630 None
631 },
632 object_file_data: self.object_file_data.clone(),
633 is_package: self.is_package,
634 link_libraries: self
635 .link_libraries
636 .iter()
637 .map(|l| l.to_memory())
638 .collect::<Result<Vec<_>, _>>()?,
639 is_stdlib: self.is_stdlib,
640 builtin_default: self.builtin_default,
641 required: self.required,
642 variant: self.variant.clone(),
643 license: self.license.clone(),
644 })
645 }
646
647 pub fn file_name(&self) -> String {
650 if let Some(idx) = self.name.rfind('.') {
651 let name = &self.name[idx + 1..self.name.len()];
652 format!("{}{}", name, self.extension_file_suffix)
653 } else {
654 format!("{}{}", self.name, self.extension_file_suffix)
655 }
656 }
657
658 pub fn resolve_path(&self, prefix: &str) -> PathBuf {
660 let mut path = PathBuf::from(prefix);
661 path.extend(self.package_parts());
662 path.push(self.file_name());
663
664 path
665 }
666
667 pub fn package_parts(&self) -> Vec<String> {
669 if let Some(idx) = self.name.rfind('.') {
670 let prefix = &self.name[0..idx];
671 prefix.split('.').map(|x| x.to_string()).collect()
672 } else {
673 Vec::new()
674 }
675 }
676
677 pub fn requires_libraries(&self) -> bool {
679 !self.link_libraries.is_empty()
680 }
681
682 pub fn is_minimally_required(&self) -> bool {
688 self.is_stdlib && (self.builtin_default || self.required)
689 }
690
691 pub fn in_libpython(&self) -> bool {
696 self.is_stdlib && (self.builtin_default || self.shared_library.is_none())
697 }
698
699 pub fn top_level_package(&self) -> &str {
701 if let Some(idx) = self.name.find('.') {
702 &self.name[0..idx]
703 } else {
704 &self.name
705 }
706 }
707}
708
709#[derive(Clone, Debug, Default)]
711pub struct PythonExtensionModuleVariants {
712 extensions: Vec<PythonExtensionModule>,
713}
714
715impl FromIterator<PythonExtensionModule> for PythonExtensionModuleVariants {
716 fn from_iter<I: IntoIterator<Item = PythonExtensionModule>>(iter: I) -> Self {
717 Self {
718 extensions: Vec::from_iter(iter),
719 }
720 }
721}
722
723impl PythonExtensionModuleVariants {
724 pub fn push(&mut self, em: PythonExtensionModule) {
725 self.extensions.push(em);
726 }
727
728 pub fn is_empty(&self) -> bool {
729 self.extensions.is_empty()
730 }
731
732 pub fn iter(&self) -> impl Iterator<Item = &PythonExtensionModule> {
733 self.extensions.iter()
734 }
735
736 pub fn default_variant(&self) -> &PythonExtensionModule {
738 &self.extensions[0]
739 }
740
741 pub fn choose_variant<S: BuildHasher>(
743 &self,
744 variants: &HashMap<String, String, S>,
745 ) -> &PythonExtensionModule {
746 let mut chosen = self.default_variant();
748
749 if let Some(preferred) = variants.get(&chosen.name) {
753 for em in self.iter() {
754 if em.variant == Some(preferred.to_string()) {
755 chosen = em;
756 break;
757 }
758 }
759 }
760
761 chosen
762 }
763}
764
765#[derive(Clone, Debug, PartialEq)]
767pub struct PythonEggFile {
768 pub data: FileData,
770}
771
772impl PythonEggFile {
773 pub fn to_memory(&self) -> Result<Self> {
774 Ok(Self {
775 data: self.data.to_memory()?,
776 })
777 }
778}
779
780#[derive(Clone, Debug, PartialEq)]
784pub struct PythonPathExtension {
785 pub data: FileData,
787}
788
789impl PythonPathExtension {
790 pub fn to_memory(&self) -> Result<Self> {
791 Ok(Self {
792 data: self.data.to_memory()?,
793 })
794 }
795}
796
797#[allow(clippy::large_enum_variant)]
799#[derive(Clone, Debug, PartialEq)]
800pub enum PythonResource<'a> {
801 ModuleSource(Cow<'a, PythonModuleSource>),
803 ModuleBytecodeRequest(Cow<'a, PythonModuleBytecodeFromSource>),
805 ModuleBytecode(Cow<'a, PythonModuleBytecode>),
807 PackageResource(Cow<'a, PythonPackageResource>),
809 PackageDistributionResource(Cow<'a, PythonPackageDistributionResource>),
811 ExtensionModule(Cow<'a, PythonExtensionModule>),
813 EggFile(Cow<'a, PythonEggFile>),
815 PathExtension(Cow<'a, PythonPathExtension>),
817 File(Cow<'a, File>),
819}
820
821impl<'a> PythonResource<'a> {
822 pub fn full_name(&self) -> String {
824 match self {
825 PythonResource::ModuleSource(m) => m.name.clone(),
826 PythonResource::ModuleBytecode(m) => m.name.clone(),
827 PythonResource::ModuleBytecodeRequest(m) => m.name.clone(),
828 PythonResource::PackageResource(resource) => {
829 format!("{}.{}", resource.leaf_package, resource.relative_name)
830 }
831 PythonResource::PackageDistributionResource(resource) => {
832 format!("{}:{}", resource.package, resource.name)
833 }
834 PythonResource::ExtensionModule(em) => em.name.clone(),
835 PythonResource::EggFile(_) => "".to_string(),
836 PythonResource::PathExtension(_) => "".to_string(),
837 PythonResource::File(f) => format!("{}", f.path().display()),
838 }
839 }
840
841 pub fn is_in_packages(&self, packages: &[String]) -> bool {
842 let name = match self {
843 PythonResource::ModuleSource(m) => &m.name,
844 PythonResource::ModuleBytecode(m) => &m.name,
845 PythonResource::ModuleBytecodeRequest(m) => &m.name,
846 PythonResource::PackageResource(resource) => &resource.leaf_package,
847 PythonResource::PackageDistributionResource(resource) => &resource.package,
848 PythonResource::ExtensionModule(em) => &em.name,
849 PythonResource::EggFile(_) => return false,
850 PythonResource::PathExtension(_) => return false,
851 PythonResource::File(_) => return false,
852 };
853
854 for package in packages {
855 if name == package || packages_from_module_name(name).contains(package) {
860 return true;
861 }
862 }
863
864 false
865 }
866
867 pub fn to_memory(&self) -> Result<Self> {
869 Ok(match self {
870 PythonResource::ModuleSource(m) => m.to_memory()?.into(),
871 PythonResource::ModuleBytecode(m) => m.to_memory()?.into(),
872 PythonResource::ModuleBytecodeRequest(m) => m.to_memory()?.into(),
873 PythonResource::PackageResource(r) => r.to_memory()?.into(),
874 PythonResource::PackageDistributionResource(r) => r.to_memory()?.into(),
875 PythonResource::ExtensionModule(m) => m.to_memory()?.into(),
876 PythonResource::EggFile(e) => e.to_memory()?.into(),
877 PythonResource::PathExtension(e) => e.to_memory()?.into(),
878 PythonResource::File(f) => f.to_memory()?.into(),
879 })
880 }
881}
882
883impl<'a> From<PythonModuleSource> for PythonResource<'a> {
884 fn from(m: PythonModuleSource) -> Self {
885 PythonResource::ModuleSource(Cow::Owned(m))
886 }
887}
888
889impl<'a> From<&'a PythonModuleSource> for PythonResource<'a> {
890 fn from(m: &'a PythonModuleSource) -> Self {
891 PythonResource::ModuleSource(Cow::Borrowed(m))
892 }
893}
894
895impl<'a> From<PythonModuleBytecodeFromSource> for PythonResource<'a> {
896 fn from(m: PythonModuleBytecodeFromSource) -> Self {
897 PythonResource::ModuleBytecodeRequest(Cow::Owned(m))
898 }
899}
900
901impl<'a> From<&'a PythonModuleBytecodeFromSource> for PythonResource<'a> {
902 fn from(m: &'a PythonModuleBytecodeFromSource) -> Self {
903 PythonResource::ModuleBytecodeRequest(Cow::Borrowed(m))
904 }
905}
906
907impl<'a> From<PythonModuleBytecode> for PythonResource<'a> {
908 fn from(m: PythonModuleBytecode) -> Self {
909 PythonResource::ModuleBytecode(Cow::Owned(m))
910 }
911}
912
913impl<'a> From<&'a PythonModuleBytecode> for PythonResource<'a> {
914 fn from(m: &'a PythonModuleBytecode) -> Self {
915 PythonResource::ModuleBytecode(Cow::Borrowed(m))
916 }
917}
918
919impl<'a> From<PythonPackageResource> for PythonResource<'a> {
920 fn from(r: PythonPackageResource) -> Self {
921 PythonResource::PackageResource(Cow::Owned(r))
922 }
923}
924
925impl<'a> From<&'a PythonPackageResource> for PythonResource<'a> {
926 fn from(r: &'a PythonPackageResource) -> Self {
927 PythonResource::PackageResource(Cow::Borrowed(r))
928 }
929}
930
931impl<'a> From<PythonPackageDistributionResource> for PythonResource<'a> {
932 fn from(r: PythonPackageDistributionResource) -> Self {
933 PythonResource::PackageDistributionResource(Cow::Owned(r))
934 }
935}
936
937impl<'a> From<&'a PythonPackageDistributionResource> for PythonResource<'a> {
938 fn from(r: &'a PythonPackageDistributionResource) -> Self {
939 PythonResource::PackageDistributionResource(Cow::Borrowed(r))
940 }
941}
942
943impl<'a> From<PythonExtensionModule> for PythonResource<'a> {
944 fn from(r: PythonExtensionModule) -> Self {
945 PythonResource::ExtensionModule(Cow::Owned(r))
946 }
947}
948
949impl<'a> From<&'a PythonExtensionModule> for PythonResource<'a> {
950 fn from(r: &'a PythonExtensionModule) -> Self {
951 PythonResource::ExtensionModule(Cow::Borrowed(r))
952 }
953}
954
955impl<'a> From<PythonEggFile> for PythonResource<'a> {
956 fn from(e: PythonEggFile) -> Self {
957 PythonResource::EggFile(Cow::Owned(e))
958 }
959}
960
961impl<'a> From<&'a PythonEggFile> for PythonResource<'a> {
962 fn from(e: &'a PythonEggFile) -> Self {
963 PythonResource::EggFile(Cow::Borrowed(e))
964 }
965}
966
967impl<'a> From<PythonPathExtension> for PythonResource<'a> {
968 fn from(e: PythonPathExtension) -> Self {
969 PythonResource::PathExtension(Cow::Owned(e))
970 }
971}
972
973impl<'a> From<&'a PythonPathExtension> for PythonResource<'a> {
974 fn from(e: &'a PythonPathExtension) -> Self {
975 PythonResource::PathExtension(Cow::Borrowed(e))
976 }
977}
978
979impl<'a> From<File> for PythonResource<'a> {
980 fn from(f: File) -> Self {
981 PythonResource::File(Cow::Owned(f))
982 }
983}
984
985impl<'a> From<&'a File> for PythonResource<'a> {
986 fn from(f: &'a File) -> Self {
987 PythonResource::File(Cow::Borrowed(f))
988 }
989}
990
991#[cfg(test)]
992mod tests {
993 use super::*;
994
995 const DEFAULT_CACHE_TAG: &str = "cpython-39";
996
997 #[test]
998 fn test_is_in_packages() {
999 let source = PythonResource::ModuleSource(Cow::Owned(PythonModuleSource {
1000 name: "foo".to_string(),
1001 source: FileData::Memory(vec![]),
1002 is_package: false,
1003 cache_tag: DEFAULT_CACHE_TAG.to_string(),
1004 is_stdlib: false,
1005 is_test: false,
1006 }));
1007 assert!(source.is_in_packages(&["foo".to_string()]));
1008 assert!(!source.is_in_packages(&[]));
1009 assert!(!source.is_in_packages(&["bar".to_string()]));
1010
1011 let bytecode = PythonResource::ModuleBytecode(Cow::Owned(PythonModuleBytecode {
1012 name: "foo".to_string(),
1013 bytecode: FileData::Memory(vec![]),
1014 optimize_level: BytecodeOptimizationLevel::Zero,
1015 is_package: false,
1016 cache_tag: DEFAULT_CACHE_TAG.to_string(),
1017 is_stdlib: false,
1018 is_test: false,
1019 }));
1020 assert!(bytecode.is_in_packages(&["foo".to_string()]));
1021 assert!(!bytecode.is_in_packages(&[]));
1022 assert!(!bytecode.is_in_packages(&["bar".to_string()]));
1023 }
1024
1025 #[test]
1026 fn package_distribution_resources_path_normalization() {
1027 let mut r = PythonPackageDistributionResource {
1030 location: PythonPackageDistributionResourceFlavor::DistInfo,
1031 package: "FoO-Bar".into(),
1032 version: "1.0".into(),
1033 name: "resource.txt".into(),
1034 data: vec![42].into(),
1035 };
1036
1037 assert_eq!(
1038 r.resolve_path("prefix"),
1039 PathBuf::from("prefix")
1040 .join("foo_bar-1.0.dist-info")
1041 .join("resource.txt")
1042 );
1043
1044 r.location = PythonPackageDistributionResourceFlavor::EggInfo;
1045
1046 assert_eq!(
1047 r.resolve_path("prefix"),
1048 PathBuf::from("prefix")
1049 .join("foo_bar-1.0.egg-info")
1050 .join("resource.txt")
1051 );
1052 }
1053}