diff --git a/Release/Product/Python/Analysis/AnalysisValue.cs b/Release/Product/Python/Analysis/AnalysisValue.cs index 8a33d4f032..42251d70d7 100644 --- a/Release/Product/Python/Analysis/AnalysisValue.cs +++ b/Release/Product/Python/Analysis/AnalysisValue.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; +using Microsoft.PythonTools.Parsing; namespace Microsoft.PythonTools.Analysis { /// @@ -25,6 +26,18 @@ public class AnalysisValue { internal AnalysisValue() { } + /// + /// Gets the name of the value if it has one, or null if it's a non-named item. + /// + /// The name property here is typically the same value you'd get by accessing __name__ + /// on the real Python object. + /// + public virtual string Name { + get { + return null; + } + } + /// /// Gets a list of locations where this value is defined. /// @@ -42,5 +55,33 @@ public class AnalysisValue { public virtual object GetConstantValue() { return Type.Missing; } + + /// + /// Returns the constant value as a string. This returns a string if the constant + /// value is either a unicode or ASCII string. + /// + public string GetConstantValueAsString() { + var constName = GetConstantValue(); + if (constName != null) { + string unicodeName = constName as string; + AsciiString asciiName; + if (unicodeName != null) { + return unicodeName; + } else if ((asciiName = constName as AsciiString) != null) { + return asciiName.String; + } + } + return null; + } + + /// + /// Returns a list of key/value pairs stored in the this object which are retrivable using + /// indexing. For lists the key values will be integers (potentially constant, potentially not), + /// for dicts the key values will be arbitrary analysis values. + /// + /// + public virtual IEnumerable, IEnumerable>> GetItems() { + yield break; + } } } diff --git a/Release/Product/Python/Analysis/PythonAnalyzer.cs b/Release/Product/Python/Analysis/PythonAnalyzer.cs index 3328e34605..689909afeb 100644 --- a/Release/Product/Python/Analysis/PythonAnalyzer.cs +++ b/Release/Product/Python/Analysis/PythonAnalyzer.cs @@ -116,11 +116,34 @@ public PythonAnalyzer(IPythonInterpreterFactory interpreterFactory) SpecializeFunction("__builtin__", "range", (n, unit, args) => unit.DeclaringModule.GetOrMakeNodeVariable(n, (nn) => new RangeInfo(_types.List, unit.ProjectState).SelfSet)); SpecializeFunction("__builtin__", "min", ReturnUnionOfInputs); SpecializeFunction("__builtin__", "max", ReturnUnionOfInputs); + SpecializeFunction("__builtin__", "getattr", SpecialGetAttr); // cached for quick checks to see if we're a call to clr.AddReference SpecializeFunction("wpf", "LoadComponent", LoadComponent); } + + private ISet SpecialGetAttr(CallExpression call, AnalysisUnit unit, ISet[] args) { + ISet res = EmptySet.Instance; + bool madeSet = false; + if (args.Length >= 2) { + if (args.Length >= 3) { + // getattr(foo, 'bar', baz), baz is a possible return value. + res = args[2]; + } + + foreach (var value in args[0]) { + foreach (var name in args[1]) { + // getattr(foo, 'bar') - attempt to do the getattr and return the proper value + var strValue = name.GetConstantValueAsString(); + if (strValue != null) { + res = res.Union(value.GetMember(call, unit, strValue), ref madeSet); + } + } + } + } + return res; + } /// /// Reloads the modules from the interpreter. @@ -378,6 +401,43 @@ class LazyModuleEnumerator { return new MemberResult[0]; } + /// + /// Specializes the provided function in the given module name to return an instance of the given type. + /// + /// The type is a fully qualified module name (e.g. thread.LockType). + /// + /// + /// + /// + public void SpecializeFunction(string moduleName, string name, string returnType) { + int lastDot; + if ((lastDot = returnType.LastIndexOf('.')) == -1) { + throw new ArgumentException(String.Format("Expected module.typename for return type, got '{0}'", returnType)); + } + + string retModule = returnType.Substring(0, lastDot); + string typeName = returnType.Substring(lastDot + 1); + + SpecializeFunction(moduleName, name, (call, unit, types) => { + ModuleReference modRef; + if (Modules.TryGetValue(retModule, out modRef)) { + if (modRef.Module != null) { + ISet res = EmptySet.Instance; + bool madeSet = false; + foreach (var value in modRef.Module.GetMember(call, unit, typeName)) { + if (value is ClassInfo) { + res = res.Union(((ClassInfo)value).Instance.SelfSet, ref madeSet); + } else { + res = res.Union(value.SelfSet, ref madeSet); + } + } + return res; + } + } + return null; + }); + } + public void SpecializeFunction(string moduleName, string name, Action dlg) { SpecializeFunction(moduleName, name, (call, unit, types) => { dlg(call, new CallInfo(types)); return null; }); } diff --git a/Release/Product/Python/Analysis/Values/BuiltinClassInfo.cs b/Release/Product/Python/Analysis/Values/BuiltinClassInfo.cs index 13cda96832..f76e500341 100644 --- a/Release/Product/Python/Analysis/Values/BuiltinClassInfo.cs +++ b/Release/Product/Python/Analysis/Values/BuiltinClassInfo.cs @@ -57,6 +57,12 @@ public BuiltinClassInfo(IPythonType classObj, PythonAnalyzer projectState) return Instance.SelfSet; } + public override string Name { + get { + return _type.Name; + } + } + public IEnumerable> GetMro() { return new[] { SelfSet }; } diff --git a/Release/Product/Python/Analysis/Values/BuiltinFunctionInfo.cs b/Release/Product/Python/Analysis/Values/BuiltinFunctionInfo.cs index 308d8306ba..c20f7fd904 100644 --- a/Release/Product/Python/Analysis/Values/BuiltinFunctionInfo.cs +++ b/Release/Product/Python/Analysis/Values/BuiltinFunctionInfo.cs @@ -56,6 +56,12 @@ public BuiltinFunctionInfo(IPythonFunction function, PythonAnalyzer projectState } } + public override string Name { + get { + return _function.Name; + } + } + public override string Description { get { if (_function.IsBuiltin) { diff --git a/Release/Product/Python/Analysis/Values/BuiltinMethodInfo.cs b/Release/Product/Python/Analysis/Values/BuiltinMethodInfo.cs index eadb0439c2..03659a49d0 100644 --- a/Release/Product/Python/Analysis/Values/BuiltinMethodInfo.cs +++ b/Release/Product/Python/Analysis/Values/BuiltinMethodInfo.cs @@ -117,7 +117,7 @@ public BuiltinMethodInfo(IPythonFunction function, PythonMemberType memType, Pyt } } - public string Name { get { return _function.Name; } } + public override string Name { get { return _function.Name; } } public override ILocatedMember GetLocatedMember() { return _function as ILocatedMember; diff --git a/Release/Product/Python/Analysis/Values/BuiltinModule.cs b/Release/Product/Python/Analysis/Values/BuiltinModule.cs index ea51407e79..c591c484fd 100644 --- a/Release/Product/Python/Analysis/Values/BuiltinModule.cs +++ b/Release/Product/Python/Analysis/Values/BuiltinModule.cs @@ -76,7 +76,7 @@ public BuiltinModule(IPythonModule module, PythonAnalyzer projectState) } } - public string Name { + public override string Name { get { return _interpreterModule.Name; } diff --git a/Release/Product/Python/Analysis/Values/ClassInfo.cs b/Release/Product/Python/Analysis/Values/ClassInfo.cs index e7e5747f5d..4764266cef 100644 --- a/Release/Product/Python/Analysis/Values/ClassInfo.cs +++ b/Release/Product/Python/Analysis/Values/ClassInfo.cs @@ -83,6 +83,12 @@ internal ClassInfo(AnalysisUnit unit, ClassDefinition klass) } } + public override string Name { + get { + return ClassDefinition.Name; + } + } + public VariableDef SubClasses { get { if (_subclasses == null) { diff --git a/Release/Product/Python/Analysis/Values/ConstantInfo.cs b/Release/Product/Python/Analysis/Values/ConstantInfo.cs index e3e9d5248b..a0da2d99ac 100644 --- a/Release/Product/Python/Analysis/Values/ConstantInfo.cs +++ b/Release/Product/Python/Analysis/Values/ConstantInfo.cs @@ -87,7 +87,7 @@ public ConstantInfo(IPythonConstant value, PythonAnalyzer projectState) return SelfSet; } - internal static ISet NumericOp(Node node, Namespace lhs, AnalysisUnit unit, PythonOperator operation, ISet rhs) { + internal static ISet NumericOp(Node node, BuiltinInstanceInfo lhs, AnalysisUnit unit, PythonOperator operation, ISet rhs) { BuiltinTypeId curType = lhs.TypeId; switch (operation) { case PythonOperator.TrueDivide: @@ -112,7 +112,7 @@ public ConstantInfo(IPythonConstant value, PythonAnalyzer projectState) break; case PythonOperator.Mod: if (lhs.TypeId == BuiltinTypeId.Str || lhs.TypeId == BuiltinTypeId.Bytes) { - return lhs.SelfSet; + return lhs.ClassInfo.Instance; } goto case PythonOperator.Add; case PythonOperator.Multiply: @@ -120,7 +120,7 @@ public ConstantInfo(IPythonConstant value, PythonAnalyzer projectState) foreach (var type in rhs) { var rhsType = type.TypeId; if (rhsType == BuiltinTypeId.Int || rhsType == BuiltinTypeId.Long) { - return lhs.SelfSet; + return lhs.ClassInfo.Instance; } } } else if (curType == BuiltinTypeId.Int || curType == BuiltinTypeId.Long) { @@ -243,7 +243,7 @@ public ConstantInfo(IPythonConstant value, PythonAnalyzer projectState) } public override string ToString() { - return ""; // " at " + hex(id(self)) + return ""; // " at " + hex(id(self)) } public override object GetConstantValue() { diff --git a/Release/Product/Python/Analysis/Values/DictionaryInfo.cs b/Release/Product/Python/Analysis/Values/DictionaryInfo.cs index b4ef4589af..4b26338d70 100644 --- a/Release/Product/Python/Analysis/Values/DictionaryInfo.cs +++ b/Release/Product/Python/Analysis/Values/DictionaryInfo.cs @@ -29,7 +29,7 @@ internal class DictionaryInfo : BuiltinInstanceInfo { private VariableDef _keyValueTupleVariable; private readonly ProjectEntry _declaringModule; private readonly int _declVersion; - private SpecializedDictionaryMethod _getMethod, _itemsMethod, _keysMethod, _valuesMethod, _iterKeysMethod, _iterValuesMethod, _popMethod, _popItemMethod, _iterItemsMethod; + private SpecializedDictionaryMethod _getMethod, _itemsMethod, _keysMethod, _valuesMethod, _iterKeysMethod, _iterValuesMethod, _popMethod, _popItemMethod, _iterItemsMethod, _updateMethod; public DictionaryInfo(ProjectEntry declaringModule) : base(declaringModule.ProjectState._dictType) { @@ -38,7 +38,6 @@ public DictionaryInfo(ProjectEntry declaringModule) _declaringModule = declaringModule; _declVersion = declaringModule.AnalysisVersion; } - public override ISet GetIndex(Node node, AnalysisUnit unit, ISet index) { return _valueTypes.Types; @@ -123,6 +122,9 @@ public DictionaryInfo(ProjectEntry declaringModule) res = GetOrMakeSpecializedMethod(ref _iterItemsMethod, "iteritems", method => new DictionaryItemsIterableBoundMethod(method, this)); } break; + case "update": + res = GetOrMakeSpecializedMethod(ref _updateMethod, "update", method => new DictionaryUpdateBoundMethod(method, this)); + break; } return res ?? base.GetMember(node, unit, name); @@ -402,6 +404,27 @@ internal DictionaryKeyValueTupleBoundMethod(BuiltinMethodInfo method, Dictionary } } + class DictionaryUpdateBoundMethod : SpecializedDictionaryMethod { + internal DictionaryUpdateBoundMethod(BuiltinMethodInfo method, DictionaryInfo myDict) + : base(method, myDict) { + } + + public override ISet Call(Node node, AnalysisUnit unit, ISet[] args, NameExpression[] keywordArgNames) { + if (args.Length >= 1) { + foreach (var type in args[0]) { + DictionaryInfo otherDict = type as DictionaryInfo; + if (otherDict != null) { + _myDict._valueTypes.AddTypes(node, unit, otherDict._valueTypes.Types); + _myDict._keyTypes.AddTypes(node, unit, otherDict._keyTypes.Types); + } + } + } + // TODO: Process keyword args and add those values to our dictionary, plus a string key + + return EmptySet.Instance; + } + } + #endregion public override string ToString() { diff --git a/Release/Product/Python/Analysis/Values/FunctionInfo.cs b/Release/Product/Python/Analysis/Values/FunctionInfo.cs index b02ba25bc8..161a783d19 100644 --- a/Release/Product/Python/Analysis/Values/FunctionInfo.cs +++ b/Release/Product/Python/Analysis/Values/FunctionInfo.cs @@ -187,6 +187,12 @@ internal FunctionInfo(AnalysisUnit unit) return false; } + public override string Name { + get { + return FunctionDefinition.Name; + } + } + public override string Description { get { StringBuilder result; @@ -219,29 +225,31 @@ internal FunctionInfo(AnalysisUnit unit) get { StringBuilder result = new StringBuilder(); bool first = true; - foreach (var ns in ReturnValue.Types) { - if (ns == null) { - continue; - } + if (ReturnValue.Types.Count <= 10) { + foreach (var ns in ReturnValue.Types) { + if (ns == null) { + continue; + } - if (ns.Push()) { - try { - if (ns.Description == null) { - continue; - } + if (ns.Push()) { + try { + if (ns.ShortDescription == null) { + continue; + } - if (first) { - result.Append(" -> "); - first = false; - } else { - result.Append(", "); - } - AppendDescription(result, ns); - } finally { + if (first) { + result.Append(" -> "); + first = false; + } else { + result.Append(", "); + } + AppendDescription(result, ns); + } finally { ns.Pop(); } - } else { - result.Append("..."); + } else { + result.Append("..."); + } } } @@ -255,7 +263,7 @@ internal FunctionInfo(AnalysisUnit unit) } else { DescriptionStack.Add(key); try { - result.Append(key.Description); + result.Append(key.ShortDescription); } finally { DescriptionStack.Pop(); } diff --git a/Release/Product/Python/Analysis/Values/ModuleInfo.cs b/Release/Product/Python/Analysis/Values/ModuleInfo.cs index e3b1cff72e..1f22ef88a5 100644 --- a/Release/Product/Python/Analysis/Values/ModuleInfo.cs +++ b/Release/Product/Python/Analysis/Values/ModuleInfo.cs @@ -194,7 +194,7 @@ internal class ModuleInfo : Namespace, IReferenceableContainer, IModule { } } - public string Name { + public override string Name { get { return _name; } } diff --git a/Release/Product/Python/Analysis/Values/NamespaceSetExtensions.cs b/Release/Product/Python/Analysis/Values/NamespaceSetExtensions.cs index baf7d20fde..9a3fd49920 100644 --- a/Release/Product/Python/Analysis/Values/NamespaceSetExtensions.cs +++ b/Release/Product/Python/Analysis/Values/NamespaceSetExtensions.cs @@ -239,7 +239,26 @@ internal static class NamespaceSetExtensions { Namespace type = null; if (types.Count == 1) { type = System.Linq.Enumerable.First(types); - } else if (types.Count > 0) { + } else if(types.Count == 2) { + var enumer = types.GetEnumerator(); + Namespace first = null, second = null; + if (enumer.MoveNext()) { + first = enumer.Current; + } + if (enumer.MoveNext()) { + second = enumer.Current; + } + + if (first != null && second != null) { + if (first.GetConstantValue() == null) { + return second; + } else if (second.GetConstantValue() == null) { + return first; + } + } + } + + if (types.Count > 0) { // simplify the types. var set = new HashSet(types, TypeUnion.UnionComparer); if (set.Count == 1) { diff --git a/Release/Product/Python/Analysis/Values/SequenceInfo.cs b/Release/Product/Python/Analysis/Values/SequenceInfo.cs index f52a6332c6..9d0c225ecf 100644 --- a/Release/Product/Python/Analysis/Values/SequenceInfo.cs +++ b/Release/Product/Python/Analysis/Values/SequenceInfo.cs @@ -20,6 +20,8 @@ using Microsoft.PythonTools.Parsing.Ast; namespace Microsoft.PythonTools.Analysis.Values { + using AnalysisKeyValuePair = KeyValuePair, IEnumerable>; + /// /// Specialized built-in instance for sequences (lists, tuples) /// @@ -140,5 +142,16 @@ public SequenceInfo(VariableDef[] indexTypes, BuiltinClassInfo seqType) public override int UnionHashCode() { return IndexTypes.Length; } + + public override IEnumerable GetItems() { + for (int i = 0; i < IndexTypes.Length; i++) { + var value = IndexTypes[i]; + + yield return new AnalysisKeyValuePair( + ProjectState.GetConstant(i), + value.Types + ); + } + } } } diff --git a/Release/Product/Python/Analysis/Values/SpecializedCallable.cs b/Release/Product/Python/Analysis/Values/SpecializedCallable.cs new file mode 100644 index 0000000000..b9667ee8e0 --- /dev/null +++ b/Release/Product/Python/Analysis/Values/SpecializedCallable.cs @@ -0,0 +1,55 @@ +/* **************************************************************************** + * + * Copyright (c) Microsoft Corporation. + * + * This source code is subject to terms and conditions of the Apache License, Version 2.0. A + * copy of the license can be found in the License.html file at the root of this distribution. If + * you cannot locate the Apache License, Version 2.0, please send an email to + * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound + * by the terms of the Apache License, Version 2.0. + * + * You must not remove this notice, or any other, from this software. + * + * ***************************************************************************/ + +using System; +using System.Collections.Generic; +using Microsoft.PythonTools.Analysis.Interpreter; +using Microsoft.PythonTools.Parsing.Ast; + +namespace Microsoft.PythonTools.Analysis.Values { + /// + /// Provides a built-in function whose analysis we deeply understand. Created with a custom delegate which + /// allows custom behavior rather than the typical behavior of returning the return type of the function. + /// + /// This is used for clr.AddReference* and calls to range() both of which we want to be customized in different + /// ways. + /// + class SpecializedCallable : SpecializedNamespace { + private readonly Func[], ISet> _call; + + public SpecializedCallable(Namespace original, Func[], ISet> call) + : base(original) { + _call = call; + } + + public SpecializedCallable(Namespace original, Namespace inst, Func[], ISet> call) + : base(original, inst) { + _call = call; + } + + public override ISet Call(Node node, AnalysisUnit unit, ISet[] args, NameExpression[] keywordArgNames) { + var realArgs = args; + if (_inst != null) { + realArgs = Utils.Concat(_inst.SelfSet, args); + } + + var analyzed = _original.Call(node, unit, args, keywordArgNames); + return _call((CallExpression)node, unit, realArgs) ?? analyzed; + } + + protected override SpecializedNamespace Clone(Namespace original, Namespace instance) { + return new SpecializedCallable(original, instance, _call); + } + } +} diff --git a/Release/Product/Python/Django/Django.csproj b/Release/Product/Python/Django/Django.csproj index c213571731..ef0402202f 100644 --- a/Release/Product/Python/Django/Django.csproj +++ b/Release/Product/Python/Django/Django.csproj @@ -214,6 +214,13 @@ true + + + + + + + Designer diff --git a/Release/Product/Python/Django/FastCgiTutorial/FastCGI Getting Started.html b/Release/Product/Python/Django/FastCgiTutorial/FastCGI Getting Started.html new file mode 100644 index 0000000000..e82dcab939 --- /dev/null +++ b/Release/Product/Python/Django/FastCgiTutorial/FastCGI Getting Started.html @@ -0,0 +1,137 @@ + + + + wfastcgi - WSGI FastCGI for IIS and Windows + + + + +

Overview

+

The FastCGI module in IIS enables popular application frameworks that support the FastCGI protocol to be hosted on the IIS Web server in a high performance and reliable way. +FastCGI provides a high-performance alternative to the Common Gateway Interface (CGI), which is a standard way of interfacing external applications with Web servers that has been a part of the supported IIS +feature set since the first release.

+ +

CGI programs are executable files that are launched by the Web server for each request to process the request and generate dynamic responses that are then sent back to the client. +Because many of these frameworks do not support multi-threaded execution, CGI enables them to execute reliably on IIS by executing exactly one request per process. Unfortunately, it provides +poor performance due to the high cost of starting and shutting down a process for each request.

+ +

FastCGI addresses the performance issues that are inherent in CGI by providing a mechanism to reuse a single process over and over again for many requests. Additionally, FastCGI +maintains compatibility with non-thread-safe libraries by providing a pool of reusable processes and ensuring that each process handles only one request at a time.

+ +

Enable FastCGI Support in IIS 7

+

Windows Server 2008

+

Go to Server Manager -> Roles -> Add Role Services. On the Select Role Services page, select the CGI check box. This enables both the CGI and FastCGI services.

+ + + +

Windows Vista SP1

+

Go to Control Panel -> Programs and Features -> Turn Windows features on or off. In the Windows Features dialog box, select the CGI check box. This enables both the CGI and FastCGI services.

+ + +

+ + IMPORTANT: Install the Update for the FastCGI Module +The update for the IIS 7 FastCGI module fixes several known compatibility issues with popular PHP applications. Install the update from one of the following locations: +

+ +

+  

+

+ Install the + Administration Pack for IIS 7

+

+ NOTE: This step is optional.

+

+ Among other useful features, the Administration Pack for IIS 7 has a convenient + user interface for configuring FastCGI settings. The Administration Pack can be + installed from the following locations:

+ + +

Install and Configure Python and Django

+ + +

Install wfastcgi

+

Currently this file just needs to be copied somewhere onto your machine, where you can refer to it in the configuring step below.

+ +

Configure IIS 7 to Handle Python Requests

+
    +
  1. Open IIS Manager. At the server level, double-click Handler Mappings.

    + +
  2. +
  3. In the Actions pane, click Add Module Mapping.... In the Add Module Mapping dialog box, specify the configuration settings as follows: + +
      +
    • Request path: * TODO: NEED GUIDANCE ON THE REQUEST PATH HERE
    • +
    • Module: FastCgiModule
    • +
    • Executable: "C:\Python27\python.exe|<path to wfastcgi.py>\wfastcgi.py"
    • +
    • Name: Python via FastCGI
    • +
    +
  4. +
  5. Click OK

    + +
  6. +
  7. In the Add Module Mapping confirmation dialog box that asks if you want to create a FastCGI application for this executable, click Yes.

    + + +
  8. + +
  9. +Add FastCGI settings. +
      +
    • In IIS Manager. At the server level, double-click FastCGI Settings.
      +
    • +
    • Click Add Application... under Actions
    • +
    • For Full Path specify the path to the Python interpreter, and for Arguments specify the path to wfastcgi.py
      +
      +
    • +
    • Click on the Environment Variables collection and define the following variables:
      + + + + + +
      NameValue
      WSGI_HANDLERdjango.core.handlers.wsgi.WSGIHandler
      DJANGO_SETTINGS_MODULEsettings
      PYTHON_PATH<path to django application>
      +
    • +
    +
  10. +
  11. Test that the handler mapping works correctly by ... DOING WHAT EXACTLY? + +
  12. +
+ + \ No newline at end of file diff --git a/Release/Product/Python/Django/FastCgiTutorial/images/AddModuleMapping.jpg b/Release/Product/Python/Django/FastCgiTutorial/images/AddModuleMapping.jpg new file mode 100644 index 0000000000..fc9a2f2147 Binary files /dev/null and b/Release/Product/Python/Django/FastCgiTutorial/images/AddModuleMapping.jpg differ diff --git a/Release/Product/Python/Django/FastCgiTutorial/images/CGI in Vista.png b/Release/Product/Python/Django/FastCgiTutorial/images/CGI in Vista.png new file mode 100644 index 0000000000..1c2dbfcae4 Binary files /dev/null and b/Release/Product/Python/Django/FastCgiTutorial/images/CGI in Vista.png differ diff --git a/Release/Product/Python/Django/FastCgiTutorial/images/IIS - Enable FastCGI Role.png b/Release/Product/Python/Django/FastCgiTutorial/images/IIS - Enable FastCGI Role.png new file mode 100644 index 0000000000..59eff5169a Binary files /dev/null and b/Release/Product/Python/Django/FastCgiTutorial/images/IIS - Enable FastCGI Role.png differ diff --git a/Release/Product/Python/Django/FastCgiTutorial/images/PyFastCgiMapping.png b/Release/Product/Python/Django/FastCgiTutorial/images/PyFastCgiMapping.png new file mode 100644 index 0000000000..eea1cadb7b Binary files /dev/null and b/Release/Product/Python/Django/FastCgiTutorial/images/PyFastCgiMapping.png differ diff --git a/Release/Product/Python/Django/FastCgiTutorial/images/handler mappings.jpg b/Release/Product/Python/Django/FastCgiTutorial/images/handler mappings.jpg new file mode 100644 index 0000000000..f7336e45d2 Binary files /dev/null and b/Release/Product/Python/Django/FastCgiTutorial/images/handler mappings.jpg differ diff --git a/Release/Product/Python/Django/Project/DjangoProject.cs b/Release/Product/Python/Django/Project/DjangoProject.cs index 65e9f811c6..1d4e9422c5 100644 --- a/Release/Product/Python/Django/Project/DjangoProject.cs +++ b/Release/Product/Python/Django/Project/DjangoProject.cs @@ -39,6 +39,8 @@ class DjangoProject : FlavoredProject { private IVsProjectFlavorCfgProvider _innerVsProjectFlavorCfgProvider; private static Guid PythonProjectGuid = new Guid("888888a0-9f3d-457c-b088-3a5042f75d52"); private static ImageList _images; + internal Dictionary _tags = new Dictionary(); + internal Dictionary _filters = new Dictionary(); #region IVsAggregatableProject @@ -74,60 +76,98 @@ class DjangoProject : FlavoredProject { analyzer.SpecializeFunction("django.template.loader", "render_to_string", RenderToStringProcessor); analyzer.SpecializeFunction("django.template.base.Library", "filter", FilterProcessor); analyzer.SpecializeFunction("django.template.base.Library", "tag", TagProcessor); + analyzer.SpecializeFunction("django.template.base.Parser", "parse", ParseProcessor); + analyzer.SpecializeFunction("django.template.base", "import_library", "django.template.base.Library"); break; } } } - // Load the icon we will be using for our nodes - /*Assembly assembly = Assembly.GetExecutingAssembly(); - nodeIcon = new Icon(assembly.GetManifestResourceStream("Microsoft.VisualStudio.VSIP.Samples.Flavor.Node.ico")); - this.FileAdded += new EventHandler(this.UpdateIcons); - this.FileRenamed += new EventHandler(this.UpdateIcons);*/ } - internal Dictionary _tags = new Dictionary(); - internal Dictionary _filters = new Dictionary(); - private void FilterProcessor(CallExpression call, CallInfo callInfo) { - if (callInfo.NormalArgumentCount >= 3) { - foreach (var name in callInfo.GetArgument(1)) { - var constName = name.GetConstantValue(); - string unicodeName = constName as string; - if (unicodeName != null) { - _filters[unicodeName] = unicodeName; - } - - AsciiString asciiName = constName as AsciiString; - if (asciiName != null) { - _filters[asciiName.String] = asciiName.String; + private void ParseProcessor(CallExpression call, CallInfo callInfo) { + // def parse(self, parse_until=None): + // We want to find closing tags here passed to parse_until... + if (callInfo.NormalArgumentCount >= 2) { + foreach (var tuple in callInfo.GetArgument(1)) { + foreach (var indexValue in tuple.GetItems()) { + var values = indexValue.Value; + foreach (var value in values) { + var str = value.GetConstantValueAsString(); + if (str != null) { + RegisterTag(_tags, str); + } + } } } } } + private static string GetConstantString(AnalysisValue value) { + var constName = value.GetConstantValue(); + if (constName != null) { + string unicodeName = constName as string; + AsciiString asciiName; + if (unicodeName != null) { + return unicodeName; + } else if ((asciiName = constName as AsciiString) != null) { + return asciiName.String; + } + } + return null; + } + + private void FilterProcessor(CallExpression call, CallInfo callInfo) { + ProcessTags(callInfo, _filters); + } + private void TagProcessor(CallExpression call, CallInfo callInfo) { + ProcessTags(callInfo, _tags); + } + + private static void ProcessTags(CallInfo callInfo, Dictionary tags) { if (callInfo.NormalArgumentCount >= 3) { + // library.filter(name, value) foreach (var name in callInfo.GetArgument(1)) { var constName = name.GetConstantValue(); - string unicodeName = constName as string; - if (unicodeName != null) { - _tags[unicodeName] = unicodeName; + if (constName == Type.Missing) { + if (name.Name != null) { + RegisterTag(tags, name.Name); + } + } else { + var strName = name.GetConstantValueAsString(); + if (strName != null) { + RegisterTag(tags, strName); + } } - - AsciiString asciiName = constName as AsciiString; - if (asciiName != null) { - _tags[asciiName.String] = asciiName.String; + } + } else if (callInfo.NormalArgumentCount >= 2) { + // library.filter(value) + foreach (var name in callInfo.GetArgument(1)) { + if (name.Name != null) { + RegisterTag(tags, name.Name); } } - } + } + } + + private static void RegisterTag(Dictionary tags, string name) { + tags[name] = name; } private void RenderToStringProcessor(CallExpression call, CallInfo callInfo) { - if (call.Args.Count == 2) { - var templateName = call.Args[0].Expression as ConstantExpression; - if (templateName != null) { - var variablesList = call.Args[1].Expression as DictionaryExpression; - if (variablesList != null) { + if (callInfo.NormalArgumentCount == 2) { + foreach (var name in callInfo.GetArgument(0)) { + var constName = name.GetConstantValue(); + var strName = constName as string; + AsciiString asciiName; + if (strName != null) { + } else if ((asciiName = constName as AsciiString) != null) { + strName = asciiName.String; + } else { + continue; + } + foreach (var dict in callInfo.GetArgument(1)) { } } } diff --git a/Release/Product/Python/Django/wfastcgi.py b/Release/Product/Python/Django/wfastcgi.py new file mode 100644 index 0000000000..46fc696899 --- /dev/null +++ b/Release/Product/Python/Django/wfastcgi.py @@ -0,0 +1,330 @@ +import sys +import struct +import cStringIO +import os +import traceback + +# http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S3 + +FCGI_VERSION_1 = 1 +FCGI_HEADER_LEN = 8 + +FCGI_BEGIN_REQUEST = 1 +FCGI_ABORT_REQUEST = 2 +FCGI_END_REQUEST = 3 +FCGI_PARAMS = 4 +FCGI_STDIN = 5 +FCGI_STDOUT = 6 +FCGI_STDERR = 7 +FCGI_DATA = 8 +FCGI_GET_VALUES = 9 +FCGI_GET_VALUES_RESULT = 10 +FCGI_UNKNOWN_TYPE = 11 +FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE + +FCGI_NULL_REQUEST_ID = 0 + +FCGI_KEEP_CONN = 1 + +FCGI_RESPONDER = 1 +FCGI_AUTHORIZER = 2 +FCGI_FILTER = 3 + +FCGI_REQUEST_COMPLETE = 0 +FCGI_CANT_MPX_CONN = 1 +FCGI_OVERLOADED = 2 +FCGI_UNKNOWN_ROLE = 3 + +FCGI_MAX_CONNS = "FCGI_MAX_CONNS" +FCGI_MAX_REQS = "FCGI_MAX_REQS" +FCGI_MPXS_CONNS = "FCGI_MPXS_CONNS" + +class FastCgiRecord(object): + """Represents a FastCgiRecord. Encapulates the type, role, flags. Holds +onto the params which we will receive and update later.""" + def __init__(self, type, req_id, role, flags): + self.type = type + self.req_id = req_id + self.role = role + self.flags = flags + self.params = {} + + def __repr__(self): + return '' % (self.type, + self.req_id, + self.role, + self.flags) + +#typedef struct { +# unsigned char version; +# unsigned char type; +# unsigned char requestIdB1; +# unsigned char requestIdB0; +# unsigned char contentLengthB1; +# unsigned char contentLengthB0; +# unsigned char paddingLength; +# unsigned char reserved; +# unsigned char contentData[contentLength]; +# unsigned char paddingData[paddingLength]; +#} FCGI_Record; + +def read_fastcgi_record(input): + """reads the main fast cgi record""" + data = input.read(8) # read record + content_size = ord(data[4]) << 8 | ord(data[5]) + + content = input.read(content_size) # read content + input.read(ord(data[6])) # read padding + + if ord(data[0]) != FCGI_VERSION_1: + raise Exception('Unknown fastcgi version ' + str(data[0])) + + req_id = (ord(data[2]) << 8) | ord(data[3]) + + reqtype = ord(data[1]) + processor = REQUEST_PROCESSORS.get(reqtype) + if processor is None: + # unknown type requested, send response + send_response(req_id, FCGI_UNKNOWN_TYPE, data[1] + '\0' * 7) + return None + + return processor(req_id, content) + + +def read_fastcgi_begin_request(req_id, content): + """reads the begin request body and updates our +_REQUESTS table to include the new request""" + # typedef struct { + # unsigned char roleB1; + # unsigned char roleB0; + # unsigned char flags; + # unsigned char reserved[5]; + # } FCGI_BeginRequestBody; + + # TODO: Ignore request if it exists + res = FastCgiRecord( + FCGI_BEGIN_REQUEST, + req_id, + (ord(content[0]) << 8) | ord(content[1]), # role + ord(content[2]), # flags + ) + _REQUESTS[req_id] = res + + +def read_fastcgi_keyvalue_pairs(content, offset): + """Reads a FastCGI key/value pair stream""" + + name_len = ord(content[offset]) + + if (name_len & 0x80) != 0: + name_full_len = content[offset:offset+4] + name_len = int_struct.unpack(name_full_len)[0] + offset += 4 + else: + offset += 1 + + value_len = ord(content[offset]) + + if (value_len & 0x80) != 0: + value_full_len = content[offset:offset+4] + value_len = int_struct.unpack(value_full_len)[0] + offset += 4 + else: + offset += 1 + + name = content[offset:offset+name_len] + offset += name_len + + value = content[offset:offset+value_len] + offset += value_len + + return offset, name, value + + +def write_name_len(io, name): + """Writes the length of a single name for a key or value in +a key/value stream""" + if len(name) <= 0x7f: + io.write(chr(len(name))) + else: + io.write(int_struct.pack(len(name))) + + +def write_fastcgi_keyvalue_pairs(pairs): + """creates a FastCGI key/value stream and returns it as a string""" + res = cStringIO.StringIO() + for key, value in pairs.iteritems(): + write_name_len(res, key) + write_name_len(res, value) + + res.write(key) + res.write(value) + + return res.getvalue() + + +def read_fastcgi_params(req_id, content): + if not content: + return None + + offset = 0 + res = _REQUESTS[req_id].params + while offset < len(content): + offset, name, value = read_fastcgi_keyvalue_pairs(content, offset) + res[name] = value + + +def read_fastcgi_input(req_id, content): + """reads FastCGI std-in and stores it in wsgi.input passed in the +wsgi environment array""" + res = _REQUESTS[req_id].params + if 'wsgi.input' not in res: + res['wsgi.input'] = content + else: + res['wsgi.input'] += content + + return _REQUESTS[req_id] + + +def read_fastcgi_data(req_id, content): + """reads FastCGI data stream and publishes it as wsgi.data""" + res = _REQUESTS[req_id].params + if 'wsgi.data' not in res: + res['wsgi.data'] = content + else: + res['wsgi.data'] += content + + +def read_fastcgi_abort_request(req_id, content): + """reads the wsgi abort request, which we ignore, we'll send the +finish execution request anyway...""" + pass + + +def read_fastcgi_get_values(req_id, content): + """reads the fastcgi request to get parameter values, and immediately +responds""" + offset = 0 + request = {} + while offset < len(content): + offset, name, value = read_fastcgi_keyvalue_pairs(content, offset) + request[name] = value + + response = {} + if FCGI_MAX_CONNS in request: + response[FCGI_MAX_CONNS] = '1' + + if FCGI_MAX_REQS in request: + response[FCGI_MAX_REQS] = '1' + + if FCGI_MPXS_CONNS in request: + response[FCGI_MPXS_CONNS] = '0' + + send_response(req_id, FCGI_GET_VALUES_RESULT, + write_fastcgi_keyvalue_pairs(response)) + + +# Formatting of 4-byte ints in network order +int_struct = struct.Struct('!i') + +# Our request processors for different FastCGI protocol requests. Only +# the requests which we receive are defined here. +REQUEST_PROCESSORS = { + FCGI_BEGIN_REQUEST : read_fastcgi_begin_request, + FCGI_ABORT_REQUEST : read_fastcgi_abort_request, + FCGI_PARAMS : read_fastcgi_params, + FCGI_STDIN : read_fastcgi_input, + FCGI_DATA : read_fastcgi_data, + FCGI_GET_VALUES : read_fastcgi_get_values +} + +def log(txt): + """Logs fatal errors to a log file if WSGI_LOG env var is defined""" + log_file = os.environ.get('WSGI_LOG') + if log_file: + with file(log_file, 'a+') as f: + f.write(txt) + + +def send_response(id, resp_type, content, streaming = True): + """sends a response w/ the given id, type, and content to the server. +If the content is streaming then an empty record is sent at the end to +terminate the stream""" + offset = 0 + while 1: + if id < 256: + id_0 = 0 + id_1 = id + else: + id_0 = id >> 8 + id_1 = id & 0xff + + # content len, padding len, content + len_remaining = len(content) - offset + if len_remaining > 65535: + len_0 = 0xff + len_1 = 0xff + content_str = content[offset:offset+65535] + offset += 65535 + else: + len_0 = len_remaining >> 8 + len_1 = len_remaining & 0xff + content_str = content[offset:] + offset += len_remaining + + data = '%c%c%c%c%c%c%c%c%s' % ( + FCGI_VERSION_1, # version + resp_type, # type + id_0, # requestIdB1 + id_1, # requestIdB0 + len_0, # contentLengthB1 + len_1, # contentLengthB0 + 0, # paddingLength + 0, # reserved + content_str) + + os.write(stdout, data) + if len_remaining == 0 or not streaming: + break + +if __name__ == '__main__': + # TODO: Pull this from an env var + handler_name = os.getenv('WSGI_HANDLER', 'django.core.handlers.wsgi.WSGIHandler') + module, callable = handler_name.rsplit('.', 1) + handler = getattr(__import__(module, fromlist=[callable]), callable)() + + stdout = sys.stdin.fileno() + try: + import msvcrt + msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) + except ImportError: + pass + + _REQUESTS = {} + + while True: + try: + record = read_fastcgi_record(sys.stdin) + if record: + record.params['wsgi.input'] = cStringIO.StringIO(record.params['wsgi.input']) + def start_response(status, headers, exc_info = None): + pass + + sys.stdout = sys.stderr = sys.__stdout__ = sys.__stderr__ = output = cStringIO.StringIO() + record.params['SCRIPT_NAME'] = '' + try: + response = ''.join(handler(record.params, start_response)) + except: + send_response(record.req_id, FCGI_STDERR, output.getvalue()) + else: + send_response(record.req_id, FCGI_STDOUT, response) + + # for testing of throughput of fastcgi handler vs static pages + #send_response(record.req_id, FCGI_STDOUT, 'Content-type: text/html\r\n\r\n\r\n\nfoo') + + send_response(record.req_id, FCGI_END_REQUEST, '\x00\x00\x00\x00\x00\x00\x00\x00', streaming=False) + del _REQUESTS[record.req_id] + except: + log(traceback.format_exc()) + + diff --git a/Release/Tests/AnalysisTest/AnalysisTest.cs b/Release/Tests/AnalysisTest/AnalysisTest.cs index c66bbd2929..bbadb5f922 100644 --- a/Release/Tests/AnalysisTest/AnalysisTest.cs +++ b/Release/Tests/AnalysisTest/AnalysisTest.cs @@ -647,6 +647,24 @@ class y(object): AssertContainsExactly(entry.GetTypesFromNameByIndex("b", 1), StringType); AssertContainsExactly(entry.GetTypesFromNameByIndex("c", 1), StringType); } + + [TestMethod] + public void TestGetAttr() { + var entry = ProcessText(@" +class x(object): + def __init__(self, value): + self.value = value + +a = x(42) +b = getattr(a, 'value') +c = getattr(a, 'dne', 'foo') +d = getattr(a, 'value', 'foo') +"); + + AssertContainsExactly(entry.GetTypesFromNameByIndex("b", 1), IntType); + AssertContainsExactly(entry.GetTypesFromNameByIndex("c", 1), StringType); + AssertContainsExactly(entry.GetTypesFromNameByIndex("d", 1), IntType, StringType); + } [TestMethod] public void TestListAppend() { @@ -1765,6 +1783,18 @@ import mod1 Assert.AreEqual(entry.GetValuesByIndex("x.iteritems().next()[1]", 1).Select(x => x.PythonType).First(), StringType); } + [TestMethod] + public void TestDictUpdate() { + var entry = ProcessText(@" +a = {42:100} +b = {} +b.update(a) +"); + + Assert.AreEqual(entry.GetValuesByIndex("b.items()[0][0]", 1).Select(x => x.PythonType).First(), IntType); + Assert.AreEqual(entry.GetValuesByIndex("b.items()[0][1]", 1).Select(x => x.PythonType).First(), IntType); + } + [TestMethod] public void TestFutureDivision() { var entry = ProcessText(@" @@ -3077,6 +3107,9 @@ class foo(object): def g(): return c.Length +def h(): + return f + return g class return_func_class: def return_func(self): @@ -3109,6 +3142,7 @@ class return_func_class: //AssertContainsExactly(GetVariableDescriptions(entry, "System.AppDomain.DomainUnload", 1), "event of type System.EventHandler"); AssertContainsExactly(GetVariableDescriptionsByIndex(entry, "None", 1), "None"); AssertContainsExactly(GetVariableDescriptionsByIndex(entry, "f.func_name", 1), "property of type str"); + AssertContainsExactly(GetVariableDescriptionsByIndex(entry, "h", 1), "def h(...) -> def f(...) -> str, def g(...)"); // method which returns it's self, we shouldn't stack overflow producing the help... AssertContainsExactly(GetVariableDescriptionsByIndex(entry, "return_func_class().return_func", 1), @"method return_func of return_func_class objects -> method return_func of return_func_class objects ...