@@ -160,6 +160,19 @@ def parse_special_directives(setup_entry, package_dir=None):
160
160
sys .path .insert (0 , package_dir )
161
161
if "." in resource :
162
162
resource , _ , attribute = resource .rpartition ("." )
163
+ package , _ , path = resource .partition ("." )
164
+ base_path = os .path .join (package_dir , package )
165
+ if path :
166
+ path = os .path .join (base_path , os .path .join (* path .split ("." )))
167
+ else :
168
+ path = base_path
169
+ if not os .path .exists (path ) and os .path .exists ("{0}.py" .format (path )):
170
+ path = "{0}.py" .format (path )
171
+ elif os .path .isdir (path ):
172
+ path = os .path .join (path , "__init__.py" )
173
+ rv = ast_parse_attribute_from_file (path , attribute )
174
+ if rv :
175
+ return str (rv )
163
176
module = importlib .import_module (resource )
164
177
rv = getattr (module , attribute )
165
178
if not isinstance (rv , six .string_types ):
@@ -203,10 +216,10 @@ def setuptools_parse_setup_cfg(path):
203
216
204
217
def get_package_dir_from_setupcfg (parser , base_dir = None ):
205
218
# type: (configparser.ConfigParser, STRING_TYPE) -> Text
206
- if not base_dir :
207
- package_dir = os .getcwd ()
208
- else :
219
+ if base_dir is not None :
209
220
package_dir = base_dir
221
+ else :
222
+ package_dir = os .getcwd ()
210
223
if parser .has_option ("options" , "packages.find" ):
211
224
pkg_dir = parser .get ("options" , "packages.find" )
212
225
if isinstance (package_dir , Mapping ):
@@ -217,6 +230,15 @@ def get_package_dir_from_setupcfg(parser, base_dir=None):
217
230
_ , pkg_dir = pkg_dir .split ("find:" )
218
231
pkg_dir = pkg_dir .strip ()
219
232
package_dir = os .path .join (package_dir , pkg_dir )
233
+ elif os .path .exists (os .path .join (package_dir , "setup.py" )):
234
+ setup_py = ast_parse_setup_py (os .path .join (package_dir , "setup.py" ))
235
+ if "package_dir" in setup_py :
236
+ package_lookup = setup_py ["package_dir" ]
237
+ if not isinstance (package_lookup , Mapping ):
238
+ return package_lookup
239
+ return package_lookup .get (
240
+ next (iter (list (package_lookup .keys ()))), package_dir
241
+ )
220
242
return package_dir
221
243
222
244
@@ -638,7 +660,7 @@ def match_assignment_name(self, match):
638
660
639
661
def ast_unparse (item , initial_mapping = False , analyzer = None , recurse = True ): # noqa:C901
640
662
# type: (Any, bool, Optional[Analyzer], bool) -> Union[List[Any], Dict[Any, Any], Tuple[Any, ...], STRING_TYPE]
641
- unparse = partial (ast_unparse , initial_mapping = initial_mapping , analyzer = analyzer )
663
+ unparse = partial (ast_unparse , initial_mapping = initial_mapping , analyzer = analyzer , recurse = recurse )
642
664
if isinstance (item , ast .Dict ):
643
665
unparsed = dict (zip (unparse (item .keys ), unparse (item .values )))
644
666
elif isinstance (item , ast .List ):
@@ -665,13 +687,35 @@ def ast_unparse(item, initial_mapping=False, analyzer=None, recurse=True): # no
665
687
unparsed = item
666
688
elif six .PY3 and isinstance (item , ast .NameConstant ):
667
689
unparsed = item .value
690
+ elif isinstance (item , ast .Attribute ):
691
+ attr_name = getattr (item , "value" , None )
692
+ attr_attr = getattr (item , "attr" , None )
693
+ name = None
694
+ if initial_mapping :
695
+ unparsed = item
696
+ elif attr_name and not recurse :
697
+ name = attr_name
698
+ else :
699
+ name = unparse (attr_name ) if attr_name is not None else attr_attr
700
+ if name and attr_attr :
701
+ if not initial_mapping and isinstance (name , six .string_types ):
702
+ unparsed = "." .join ([item for item in (name , attr_attr ) if item ])
703
+ else :
704
+ unparsed = item
705
+ elif attr_attr and not name and not initial_mapping :
706
+ unparsed = attr_attr
707
+ else :
708
+ unparsed = name if not unparsed else unparsed
668
709
elif isinstance (item , ast .Call ):
669
710
unparsed = {}
670
711
if isinstance (item .func , ast .Name ):
671
- name = unparse (item .func )
672
- unparsed [name ] = {}
712
+ func_name = unparse (item .func )
713
+ elif isinstance (item .func , ast .Attribute ):
714
+ func_name = unparse (item .func )
715
+ if func_name :
716
+ unparsed [func_name ] = {}
673
717
for keyword in item .keywords :
674
- unparsed [name ].update (unparse (keyword ))
718
+ unparsed [func_name ].update (unparse (keyword ))
675
719
elif isinstance (item , ast .keyword ):
676
720
unparsed = {unparse (item .arg ): unparse (item .value )}
677
721
elif isinstance (item , ast .Assign ):
@@ -681,7 +725,7 @@ def ast_unparse(item, initial_mapping=False, analyzer=None, recurse=True): # no
681
725
# XXX: Original reference
682
726
if not initial_mapping :
683
727
target = unparse (next (iter (item .targets )), recurse = False )
684
- val = unparse (item .value )
728
+ val = unparse (item .value , recurse = False )
685
729
if isinstance (target , (tuple , set , list )):
686
730
unparsed = dict (zip (target , val ))
687
731
else :
@@ -704,15 +748,48 @@ def ast_unparse(item, initial_mapping=False, analyzer=None, recurse=True): # no
704
748
return unparsed
705
749
706
750
707
- def ast_parse_setup_py (path ):
708
- # type: (S) -> Dict[Any, Any]
751
+ def ast_parse_attribute_from_file (path , attribute ):
752
+ # type: (S) -> Any
753
+ analyzer = ast_parse_file (path )
754
+ target_value = None
755
+ for k , v in analyzer .assignments .items ():
756
+ name = ""
757
+ if isinstance (k , ast .Name ):
758
+ name = k .id
759
+ elif isinstance (k , ast .Attribute ):
760
+ fn = ast_unparse (k )
761
+ if isinstance (fn , six .string_types ):
762
+ _ , _ , name = fn .rpartition ("." )
763
+ if name == attribute :
764
+ target_value = ast_unparse (v , analyzer = analyzer )
765
+ break
766
+ if isinstance (target_value , Mapping ) and attribute in target_value :
767
+ return target_value [attribute ]
768
+ return target_value
769
+
770
+
771
+ def ast_parse_file (path ):
772
+ # type: (S) -> Analyzer
709
773
with open (path , "r" ) as fh :
710
774
tree = ast .parse (fh .read ())
711
775
ast_analyzer = Analyzer ()
712
776
ast_analyzer .visit (tree )
777
+ return ast_analyzer
778
+
779
+
780
+ def ast_parse_setup_py (path ):
781
+ # type: (S) -> Dict[Any, Any]
782
+ ast_analyzer = ast_parse_file (path )
713
783
setup = {} # type: Dict[Any, Any]
714
784
for k , v in ast_analyzer .function_map .items ():
715
- if isinstance (k , ast .Name ) and k .id == "setup" :
785
+ fn_name = ""
786
+ if isinstance (k , ast .Name ):
787
+ fn_name = k .id
788
+ elif isinstance (k , ast .Attribute ):
789
+ fn = ast_unparse (k )
790
+ if isinstance (fn , six .string_types ):
791
+ _ , _ , fn_name = fn .rpartition ("." )
792
+ if fn_name == "setup" :
716
793
setup = v
717
794
cleaned_setup = ast_unparse (setup , analyzer = ast_analyzer )
718
795
return cleaned_setup
0 commit comments