1+ from __future__ import annotations
2+
3+ import copy
14import hashlib
25import json
36import pathlib
47import typing
58
9+ from fnmatch import fnmatchcase
610from typing import Dict , List , Optional , Union
711
812from pydantic import BaseModel , Field , validator
@@ -24,23 +28,126 @@ class _BaseDependency(StrictModel):
2428 def sorted_extras (cls , v : List [str ]) -> List [str ]:
2529 return sorted (v )
2630
31+ def _merge_base (self , other : _BaseDependency ) -> _BaseDependency :
32+ if other is None :
33+ return self
34+ if (
35+ self .name != other .name
36+ or self .manager != other .manager
37+ or self .category != other .category
38+ ):
39+ raise ValueError (
40+ "Cannot merge incompatible dependencies: {self} != {other}"
41+ )
42+ return _BaseDependency (
43+ name = self .name ,
44+ manager = self .manager ,
45+ category = self .category ,
46+ extras = list (set (self .extras + other .extras )),
47+ )
48+
2749
2850class VersionedDependency (_BaseDependency ):
2951 version : str
3052 build : Optional [str ] = None
3153 conda_channel : Optional [str ] = None
3254
55+ @staticmethod
56+ def _merge_matchspec (
57+ matchspec1 : Optional [str ], matchspec2 : Optional [str ]
58+ ) -> Optional [str ]:
59+ if matchspec1 == matchspec2 :
60+ return copy .copy (matchspec1 )
61+ if matchspec1 is None or matchspec1 == "" :
62+ return copy .copy (matchspec2 )
63+ if matchspec2 is None or matchspec2 == "" :
64+ return copy .copy (matchspec1 )
65+ if fnmatchcase (matchspec1 , matchspec2 ):
66+ return copy .copy (matchspec1 )
67+ if fnmatchcase (matchspec2 , matchspec1 ):
68+ return copy .copy (matchspec2 )
69+ return f"{ matchspec1 } ,{ matchspec2 } "
70+
71+ def merge (self , other : Optional [VersionedDependency ]) -> VersionedDependency :
72+ if other is None :
73+ return self
74+
75+ if (
76+ self .conda_channel is not None
77+ and other .conda_channel is not None
78+ and self .conda_channel != other .conda_channel
79+ ):
80+ raise ValueError (
81+ f"VersionedDependency has two different conda_channels:\n { self } \n { other } "
82+ )
83+ merged_base = self ._merge_base (other )
84+ return VersionedDependency (
85+ name = merged_base .name ,
86+ manager = merged_base .manager ,
87+ category = merged_base .category ,
88+ extras = merged_base .extras ,
89+ version = self ._merge_matchspec (self .version , other .version ), # type: ignore
90+ build = self ._merge_matchspec (self .build , other .build ),
91+ conda_channel = self .conda_channel or other .conda_channel ,
92+ )
93+
3394
3495class URLDependency (_BaseDependency ):
3596 url : str
3697 hashes : List [str ]
3798
99+ def merge (self , other : Optional [URLDependency ]) -> URLDependency :
100+ if other is None :
101+ return self
102+ if self .url != other .url :
103+ raise ValueError (f"URLDependency has two different urls:\n { self } \n { other } " )
104+
105+ if self .hashes != other .hashes :
106+ raise ValueError (
107+ f"URLDependency has two different hashess:\n { self } \n { other } "
108+ )
109+ merged_base = self ._merge_base (other )
110+
111+ return URLDependency (
112+ name = merged_base .name ,
113+ manager = merged_base .manager ,
114+ category = merged_base .category ,
115+ extras = merged_base .extras ,
116+ url = self .url ,
117+ hashes = self .hashes ,
118+ )
119+
38120
39121class VCSDependency (_BaseDependency ):
40122 source : str
41123 vcs : str
42124 rev : Optional [str ] = None
43125
126+ def merge (self , other : Optional [VCSDependency ]) -> VCSDependency :
127+ if other is None :
128+ return self
129+ if self .source != other .source :
130+ raise ValueError (
131+ f"VCSDependency has two different sources:\n { self } \n { other } "
132+ )
133+
134+ if self .vcs != other .vcs :
135+ raise ValueError (f"VCSDependency has two different vcss:\n { self } \n { other } " )
136+
137+ if self .rev is not None and other .rev is not None and self .rev != other .rev :
138+ raise ValueError (f"VCSDependency has two different revs:\n { self } \n { other } " )
139+ merged_base = self ._merge_base (other )
140+
141+ return VCSDependency (
142+ name = merged_base .name ,
143+ manager = merged_base .manager ,
144+ category = merged_base .category ,
145+ extras = merged_base .extras ,
146+ source = self .source ,
147+ vcs = self .vcs ,
148+ rev = self .rev or other .rev ,
149+ )
150+
44151
45152Dependency = Union [VersionedDependency , URLDependency , VCSDependency ]
46153
0 commit comments