private static string GetTypeHint(TypeDecl decl, ElementDecl element)
        {
            string GetParamNamespace(TypeDecl declaration, IDeclKey key) =>
            !PyExtensions.IsPackageEquals(declaration, key) ? PyExtensions.GetAlias(key) + "." : "";

            string GetFinalHint(string typeHint) =>
            element.Vector == YesNo.Y ? $"List[{typeHint}]" : typeHint;

            if (element.Value != null)
            {
                string hint = GetValue(element.Value);
                return(GetFinalHint(hint));
            }
            else if (element.Data != null)
            {
                string paramNamespace = GetParamNamespace(decl, element.Data);
                string hint           = $"{paramNamespace}{element.Data.Name}";
                return(GetFinalHint(hint));
            }
            else if (element.Key != null)
            {
                return(GetFinalHint("str"));
            }
            else if (element.Enum != null)
            {
                string paramNamespace = GetParamNamespace(decl, element.Enum);
                string hint           = $"{paramNamespace}{element.Enum.Name}";
                return(GetFinalHint(hint));
            }
            else
            {
                throw new ArgumentException("Can't deduct type");
            }
        }
 private static string GetTypeHint(TypeDecl decl, ParamDecl parameter)
 {
     if (parameter.Value != null)
     {
         string result = GetValue(parameter.Value);
         return(parameter.Vector == YesNo.Y ? $"List[{result}]" : result);
     }
     else if (parameter.Data != null)
     {
         string paramNamespace = !PyExtensions.IsPackageEquals(decl, parameter.Data)
             ? PyExtensions.GetAlias(parameter.Data) + "."
             : "";
         string result = $"{paramNamespace}{parameter.Data.Name}";
         return(parameter.Vector == YesNo.Y ? $"List[{result}]" : result);
     }
     else if (parameter.Key != null)
     {
         return(parameter.Vector == YesNo.Y ? "List[str]" : "str");
     }
     else if (parameter.Enum != null)
     {
         string paramNamespace = !PyExtensions.IsPackageEquals(decl, parameter.Enum)
             ? PyExtensions.GetAlias(parameter.Enum) + "."
             : "";
         string result = $"{paramNamespace}{parameter.Enum.Name}";
         return(parameter.Vector == YesNo.Y ? $"List[{result}]" : result);
     }
     else
     {
         throw new ArgumentException("Can't deduct type");
     }
 }
        /// <summary>
        /// Generate python classes from declaration.
        /// </summary>
        public static string Build(TypeDecl decl, List <IDecl> declarations)
        {
            var writer = new CodeWriter();

            string name = decl.Name;

            // Determine if we are inside datacentric package
            // based on module name. This affects the imports
            // and namespace use.
            bool insideDc = PyExtensions.GetPackage(decl) == "datacentric";

            // If not generating for DataCentric package, use dc. namespace
            // in front of datacentric types, otherwise use no prefix
            string dcNamespacePrefix = insideDc ? "" : "dc.";

            PythonImportsBuilder.WriteImports(decl, declarations, writer);

            writer.AppendNewLineWithoutIndent();
            writer.AppendNewLineWithoutIndent();

            // Get base classes for current declaration
            List <string> bases = new List <string>();

            if (decl.Keys.Any())
            {
                bases.Add(dcNamespacePrefix + "Record");
            }
            else if (decl.Inherit != null)
            {
                // Full package name and short namespace of the parent class,
                // or null if there is no parent
                bool   parentClassInDifferentModule = !PyExtensions.IsPackageEquals(decl, decl.Inherit);
                string parentPackage = PyExtensions.GetPackage(decl.Inherit);
                string parentClassNamespacePrefix =
                    parentClassInDifferentModule ? PyExtensions.GetAlias(parentPackage) + "." : "";
                bases.Add(parentClassNamespacePrefix + decl.Inherit.Name);
            }
            else
            {
                bases.Add(dcNamespacePrefix + "Data");
            }

            if (decl.Kind == TypeKind.Abstract)
            {
                bases.Add("ABC");
            }

            // Python 3.8:
            // if (decl.Kind == TypeKind.Final)
            // writer.AppendLine("@final");
            writer.AppendLine("@attr.s(slots=True, auto_attribs=True)");
            writer.AppendLine($"class {name}({string.Join(", ", bases)}):");
            writer.PushIndent();
            writer.AppendLines(CommentHelper.PyComment(decl.Comment));

            writer.AppendNewLineWithoutIndent();
            if (!decl.Elements.Any())
            {
                writer.AppendLine("pass");
            }

            foreach (var element in decl.Elements)
            {
                // TODO: Should be replaced with callable with specific format instead of skipping
                string skipRepresentation = element.Vector == YesNo.Y ? ", repr=False" : "";

                writer.AppendLine($"{element.Name.Underscore()}: {GetTypeHint(decl, element)} = attr.ib(default=None, kw_only=True{skipRepresentation}{GetMetaData(element)})");
                writer.AppendLines(CommentHelper.PyComment(element.Comment));
                if (element != decl.Elements.Last())
                {
                    writer.AppendNewLineWithoutIndent();
                }
            }

            // Add to_key and create_key() methods
            if (decl.Keys.Any())
            {
                var keyElements = decl.Elements.Where(e => decl.Keys.Contains(e.Name)).ToList();

                writer.AppendNewLineWithoutIndent();
                writer.AppendLine("def to_key(self) -> str:");
                writer.PushIndent();
                writer.AppendLine(CommentHelper.PyComment($"Get {decl.Name} key."));
                writer.AppendLines($"return '{decl.Name}='{GetToKeyArgs(decl.Name, keyElements, true)}");
                writer.PopIndent();

                writer.AppendNewLineWithoutIndent();

                var    namedParams       = keyElements.Select(e => $"{e.Name.Underscore()}: {GetTypeHint(decl, e)}").ToList();
                var    joinedNamedParams = string.Join(", ", namedParams);
                string start             = "def create_key(";

                // Check if tokens should be separated by new line
                if (4 + start.Length + joinedNamedParams.Length > 120)
                {
                    var indent = new string(' ', start.Length);
                    joinedNamedParams = string.Join("," + Environment.NewLine + indent, namedParams);
                }

                writer.AppendLine("@classmethod");
                writer.AppendLines($"def create_key(cls, *, {joinedNamedParams}) -> str:");

                writer.PushIndent();
                writer.AppendLine(CommentHelper.PyComment($"Create {decl.Name} key."));
                writer.AppendLines($"return '{decl.Name}='{GetToKeyArgs(decl.Name, keyElements, false)}");
                writer.PopIndent();
            }

            if (decl.Declare != null)
            {
                writer.AppendNewLineWithoutIndent();
                WriteMethods(decl, writer);
            }

            // Class end
            writer.PopIndent();

            return(writer.ToString());
        }
        /// <summary>
        /// Add import statements for given declaration.
        /// </summary>
        public static void WriteImports(TypeDecl decl, List <IDecl> declarations, CodeWriter writer)
        {
            // Always import attr module
            writer.AppendLine("import attr");


            // Instant is generated as Union[dt.datetime, dc.Instant] thus dt import is required
            if (decl.Elements.Any(e => e.Value != null && (e.Value.Type == ValueParamType.Instant ||
                                                           e.Value.Type == ValueParamType.NullableInstant)))
            {
                writer.AppendLine("import datetime as dt");
            }

            // If type is abstract - ABC import is needed
            if (decl.Kind == TypeKind.Abstract)
            {
                writer.AppendLine("from abc import ABC");
            }

            // Check if ObjectId is used
            bool hasObjectId = decl.Elements.Any(e => e.Value != null &&
                                                 (e.Value.Type == ValueParamType.TemporalId ||
                                                  e.Value.Type == ValueParamType.NullableTemporalId));

            if (hasObjectId)
            {
                writer.AppendLine("from bson import ObjectId");
            }

            // Check imports from typing
            var typingImports = new List <string>();

            // Python 3.8
            // if (decl.Keys.Any() || decl.Kind == TypeKind.Final)
            //     typingImports.Add("final");

            if (decl.Elements.Any(e => e.Vector == YesNo.Y))
            {
                typingImports.Add("List");
            }

            if (typingImports.Any())
            {
                var items = string.Join(", ", typingImports);
                writer.AppendLine($"from typing import {items}");
            }

            bool insideDc = PyExtensions.GetPackage(decl) == "datacentric";

            List <string> packagesToImport  = new List <string>();
            List <string> individualImports = new List <string>();

            // Import parent class package as its namespace, or if inside the same package,
            // import individual class instead
            if (decl.Inherit != null)
            {
                if (PyExtensions.IsPackageEquals(decl, decl.Inherit))
                {
                    IDecl parentDecl = declarations.FindByKey(decl.Inherit);
                    individualImports.Add($"from {parentDecl.Category} import {decl.Inherit.Name}");
                }
                else
                {
                    packagesToImport.Add(PyExtensions.GetPackage(decl.Inherit));
                }
            }
            // Import datacentric package as dc, or if inside datacentric,
            // import individual classes instead
            else if (decl.IsRecord)
            {
                if (insideDc)
                {
                    individualImports.Add("from datacentric.storage.record import Record");
                }
                else
                {
                    packagesToImport.Add("datacentric");
                }
            }
            // First child class of Data
            else
            {
                if (insideDc)
                {
                    individualImports.Add("from datacentric.storage.data import Data");
                }
                else
                {
                    packagesToImport.Add("datacentric");
                }
            }

            foreach (var data in decl.Elements.Where(d => d.Data != null).Select(d => d.Data))
            {
                if (PyExtensions.IsPackageEquals(decl, data))
                {
                    IDecl dataDecl = declarations.FindByKey(data);
                    individualImports.Add($"from {dataDecl.Category} import {data.Name}");
                }
                else
                {
                    packagesToImport.Add(PyExtensions.GetPackage(data));
                }
            }

            foreach (var enumElement in decl.Elements.Where(d => d.Enum != null).Select(d => d.Enum))
            {
                if (PyExtensions.IsPackageEquals(decl, enumElement))
                {
                    IDecl enumDecl = declarations.FindByKey(enumElement);
                    individualImports.Add($"from {enumDecl.Category} import {enumElement.Name}");
                }
                else
                {
                    packagesToImport.Add(PyExtensions.GetPackage(enumElement));
                }
            }

            foreach (var package in packagesToImport.Distinct())
            {
                writer.AppendLine($"import {package} as {PyExtensions.GetAlias(package)}");
            }

            foreach (var import in individualImports.Distinct())
            {
                writer.AppendLine(import);
            }
        }