Como posso obter programaticamente o caminho do “Python.exe” usado pelo ArcMap


16

Estou trabalhando com um suplemento do ArcMap em c #. No código C #, eu executei alguns scripts Python. Agora, para executar esses scripts, tenho um caminho python codificado. Mas isso não é portátil. Então, eu quero pegar o caminho do executável Python a partir do código e usá-lo.

Questão:

Como posso obter o caminho do executável Python usado pelo ArcMap a partir do código C #?

EDIT:

De suas sugestões, por enquanto estou usando o "caminho do ambiente" para obter o caminho do Python.

//get python path from environtment variable
string GetPythonPath()
{
    IDictionary environmentVariables = Environment.GetEnvironmentVariables();
    string pathVariable = environmentVariables["Path"] as string;
    if (pathVariable != null)
    {
        string[] allPaths = pathVariable.Split(';');
        foreach (var path in allPaths)
        {
            string pythonPathFromEnv = path + "\\python.exe";
            if (File.Exists(pythonPathFromEnv))
                return pythonPathFromEnv;
        }
    }
}

Mas há um problema:

Quando uma versão diferente do python é instalada na minha máquina, não há garantia de que o "python.exe" que estou usando, o ArcGIS também o utilize.

Não gosto de usar outra ferramenta para obter o caminho "python.exe" . Então, eu realmente acho que se existe alguma maneira de obter o caminho da chave do Registro. Para o registro "ArcGIS10.0", é semelhante a: insira a descrição da imagem aqui

E para isso, estou pensando em seguir o caminho para seguir o caminho:

//get python path from registry key
string GetPythonPath()
{
    const string regKey = "Python";
    string pythonPath = null;
    try
    {
        RegistryKey registryKey = Registry.LocalMachine;
        RegistryKey subKey = registryKey.OpenSubKey("SOFTWARE");
        if (subKey == null)
            return null;

        RegistryKey esriKey = subKey.OpenSubKey("ESRI");
        if (esriKey == null)
            return null;

        string[] subkeyNames = esriKey.GetSubKeyNames();//get all keys under "ESRI" key
        int index = -1;
     /*"Python" key contains arcgis version no in its name. So, the key name may be 
     varied version to version. For ArcGIS10.0, key name is: "Python10.0". So, from
     here I can get ArcGIS version also*/
        for (int i = 0; i < subkeyNames.Length; i++)
        {
            if (subkeyNames[i].Contains("Python"))
            {
                index = i;
                break;
            }
        }
        if(index < 0)
            return null;
        RegistryKey pythonKey = esriKey.OpenSubKey(subkeyNames[index]);

        string arcgisVersion = subkeyNames[index].Remove(0, 6); //remove "python" and get the version
        var pythonValue = pythonKey.GetValue("Python") as string;
        if (pythonValue != "True")//I guessed the true value for python says python is installed with ArcGIS.
            return;

        var pythonDirectory = pythonKey.GetValue("PythonDir") as string;
        if (pythonDirectory != null && Directory.Exists(pythonDirectory))
        {
            string pythonPathFromReg = pythonDirectory + "ArcGIS" + arcgisVersion + "\\python.exe";
            if (File.Exists(pythonPathFromReg))
                pythonPath = pythonPathFromReg;
        }  
    }
    catch (Exception e)
    {
        MessageBox.Show(e + "\r\nReading registry " + regKey.ToUpper());
        pythonPath = null;
    }
    return pythonPath ;
}

Mas antes de usar o segundo procedimento, preciso ter certeza sobre meus palpites. As suposições são:

  1. o "True" associado ao python significa que o python está instalado com o ArcGIS
  2. O ArcGIS 10.0 e a chave de registro da versão superior serão gravadas no mesmo processo.

Por favor, ajude-me a obter esclarecimentos sobre minhas suposições.


5
Você já pensou em criar uma ferramenta de script e executá-la no ArcObjects ?
blah238

3
Você não pode apenas ter uma variável de ambiente PATH definida como ArcGIS Python exe um requisito de instalação para o seu suplemento?
Chad Cooper

Tudo dito, o pensamento de @ ChadCooper deve ser o melhor caminho. Em vez de tentar trabalhar de trás para frente, basta configurá-lo de uma vez por todas no momento da instalação.
22712 elrobis

@elrobis: Eu sei, definir o caminho no ambiente PATH é uma boa maneira. Mas eu queria saber se existe alguma maneira de encontrar o python e fazer tudo sem interromper o usuário.
Emi

@ blah238 obrigado pela sua sugestão. Eu nunca trabalhei com a ferramenta de script. Pode ser que eu preciso aprender sobre ele
Emi

Respostas:


2

Peguei seu segundo exemplo de código, fiz funcionar em sistemas operacionais de 64 e 32 bits e simplifiquei um pouco. Funciona para mim na versão 10.1 no Windows 7 de 64 bits, mas obviamente você deve testá-lo no maior número possível de ambientes e adicionar novamente as verificações de programação defensiva que achar necessárias.

Após testar a instalação limpa do ArcGIS Desktop 10.1 sem Python, descobri que ela não inclui a subchave Python10.x, muito menos o valor True / False "Python" (ainda não sabe ao certo o que é isso, talvez entre em contato com o suporte da ESRI, se necessário) conhecer).

string GetPythonPath()
{
    string pythonPath = null;
    var localmachineKey = Registry.LocalMachine;
    // Check whether we are on a 64-bit OS by checking for the Wow6432Node key (32-bit version of the Software registry key)
    var softwareKey = localmachineKey.OpenSubKey(@"SOFTWARE\Wow6432Node"); // This is the correct key for 64-bit OS's
    if (softwareKey == null) {
        softwareKey = localmachineKey.OpenSubKey("SOFTWARE"); // This is the correct key for 32-bit OS's
    }
    var esriKey = softwareKey.OpenSubKey("ESRI");
    var realVersion = (string)esriKey.OpenSubKey("ArcGIS").GetValue("RealVersion"); // Get the "real", canonical version of ArcGIS
    var shortVersion = String.Join(".", realVersion.Split('.').Take(2).ToArray()); // Get just the Major.Minor part of the version number, e.g. 10.1
    var pythonKey = esriKey.OpenSubKey("Python" + shortVersion); // Open the Python10.x sub-key
    if (pythonKey == null) {
        throw new InvalidOperationException("Python not installed with ArcGIS!");
    }
    var pythonDirectory = (string)pythonKey.GetValue("PythonDir");
    if (Directory.Exists(pythonDirectory))
    {
        // Build path to python.exe
        string pythonPathFromReg = Path.Combine(Path.Combine(pythonDirectory, "ArcGIS" + shortVersion), "python.exe");
        if (File.Exists(pythonPathFromReg)) {
            pythonPath = pythonPathFromReg;
        }
    }
    return pythonPath;
}

Em uma máquina Desktop 10.1 com Python, isso retorna C:\Python27\ArcGIS10.1\python.exe. Em uma máquina Desktop 10.1 sem Python, isso gera uma InvalidOperationException porque a chave Python10.x não está presente.

Espero que isso ajude você com o que você está tentando realizar, o que - surpreendentemente - ainda não está claro para mim.


7

Em vez de procurar o executável Python, este tópico da ajuda sugere descascar cmd.exee executar python.exesem qualificar sua localização. Observe, no entanto, que isso deve funcionar porque o instalador do ArcGIS Desktop configura (editar: recentemente testado em 10.1, não) depende do caminho para python.exeser adicionado à PATHvariável de ambiente do usuário .

Outra abordagem é criar uma ferramenta de script e executá-la no ArcObjects .

Se você realmente está atrás do caminho para a versão do ArcGIS python.exe, por extensão da abordagem da ferramenta de script ArcObjects +, pode criar uma ferramenta de script Python cuja única saída é o valor de sys.exec_prefix. Este é o caminho da pasta que contém a versão do Python do ArcGIS, por exemplo C:\Python27\ArcGIS10.1.

Nota lateral : sys.executableretorna o caminho para ArcMap.exee NÃO python.exequando executado em processo, razão pela qual não sugiro o uso dessa variável.

Chame a ferramenta de script do ArcObjects e obtenha a saída do IGeoProcessorResultobjeto retornado .

Atualização: Aqui está um exemplo de projeto de complemento do ArcMap (VS2010, .NET 3.5) que usa uma ferramenta de script empacotada no suplemento que simplesmente exibe o caminho para o python.exeusado pelo ArcMap: http://wfurl.com/cbd5091

É apenas um botão no qual você clica e aparece uma caixa de mensagens com o caminho:

Botão Caixa de mensagem

Os bits interessantes do código:

  • Script Python:

    import sys
    import os
    import arcpy
    
    def getPythonPath():
        pydir = sys.exec_prefix
        pyexe = os.path.join(pydir, "python.exe")
        if os.path.exists(pyexe):
            return pyexe
        else:
            raise RuntimeError("No python.exe found in {0}".format(pydir))
    
    if __name__ == "__main__":
        pyexe = getPythonPath()
        arcpy.AddMessage("Python Path: {0}".format(pyexe))
        arcpy.SetParameterAsText(0, pyexe)
  • Função C #:

    public string GetPythonPath()
    {
        // Build the path to the PythonPathToolbox
        string toolboxPath = Path.Combine(Path.GetDirectoryName(this.GetType().Assembly.Location), "PythonPath.tbx");
    
        // Initialize the geoprocessor.
        IGeoProcessor2 gp = new ESRI.ArcGIS.Geoprocessing.GeoProcessorClass();
    
        // Add the PythonPath toolbox.
        gp.AddToolbox(toolboxPath);
    
        // Need an empty array even though we have no input parameters
        IVariantArray parameters = new VarArrayClass();
    
        // Execute the model tool by name.
        var result = gp.Execute("GetPythonPath", parameters, null);
        return result.GetOutput(0).GetAsText();
    }

2
Mas este documento não diz que, o instalador do ArcGIS Desktop configura o caminho para python.exe para a variável de ambiente PATH do usuário. Portanto, é possível que o caminho do python não esteja na variável de ambiente PATH. Em seguida, ele criará um erro. Portanto, como posso ter certeza de que o caminho do executável python está na variável de ambiente PATH do usuário.
Emi

2
Você não pode, como a maioria das coisas na vida e na computação, tudo o que você pode fazer é fazer suposições e esperar que as coisas funcionem e ter um plano de fallback quando não funcionar (forneça instruções sobre como adicioná-lo à variável de ambiente PATH). Dito isto, se for uma instalação normal, acredito que o instalador do ArcGIS Desktop adicione esse caminho à variável de ambiente PATH.
blah238

2
Eu já vi muitas instalações em que o python instalado pelo arcgis não estava no caminho. E se houver duas versões instaladas e a 'errada' estiver no caminho?
Blindjesse

Eu ofereci uma solução no meu terceiro parágrafo que deveria encontrar a instalação do Python no ArcGIS, independentemente da PATHvariável de ambiente.
blah238

@ blah238 não criar ferramenta de script torna meu suplemento menos portátil ou dificulta o processo de instalação do suplemento em outras máquinas?
Emi

6

Você terá acesso ao registro?

Ao instalar o ArcMap, ele instalará o Python, se não conseguir encontrá-lo. Ele procura no registro para ver se o Python já está instalado. Acredito que o local padrão do registro para isso seja: computer \ HKEY_LOCAL_MACHINE \ SOFTWARE \ PYTHON \ PythonCore \ 2.7 \ InstallPath Com uma chave padrão do local do caminho (2.7 sendo 10.1, 2.6 sendo 10.0)

Não consigo pensar em uma razão pela qual / por que o valor dessa chave esteja incorreto, mas você sempre pode seguir este caminho: Dentro da seção Esri \ Desktop do registro há um local em Python. É o caminho simples que você pode obter e criar outros caminhos para garantir a existência de um Python.exe. Por exemplo, a chave em uma máquina de 64 bits é instalada em: computer \ HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ ESRI \ Python10.1 Com uma chave PythonDir e o valor associado do caminho

Mas eu gosto da resposta @ blah238. Basta abrir um prompt do seu programa e executá-lo lá. Não vejo uma razão para que isso não funcione.


2
Essa abordagem também é falha, pois pode haver várias instalações Python e você não tem um meio direto de determinar programaticamente qual é a usada pelo ArcGIS. Se você não estiver executando o arcpy, isso talvez não importe. Ainda assim, acho que a solução mais robusta envolveria o registro e um pouco de lógica. Eu não vou lá, no entanto.
blah238

Bem, a lógica teria que começar com a versão mais recente 2.7 e trabalhar para trás. Obviamente, isso pode falhar se você instalou uma nova versão do Python e instalou uma versão mais antiga do ArcGIS que instalaria uma versão mais antiga do Python. Então, sim, eu concordo que existe esse potencial, mas não é provável (ou você pode simplesmente construir uma pesquisa de arc10.1 = py2.7, arc10.0 = py26 ... etc para ter 100% de certeza). Como eu disse, o melhor método provavelmente seria apenas desembolsar no prompt de comando.
KHibma

@KHibma Eu procurei no registro. Mas acho que é realmente incômodo se eu olhar através da tecla "PYTHON". Na minha máquina, há duas versões python instaladas e ela retorna ambas. Eu acho que é uma boa ideia procurar na chave "ESRI" e, se houver uma subchave "Python" com valor verdadeiro, posso usar o valor da subchave "PythonDir". Funciona no meu caso :)
Emi

Esta é uma maneira errada de procurar o caminho python no registro através da chave "ESRI"? Ou existe alguma chance de que, a maneira como a esri use para criar chaves e valores no registro possa ser alterada e o código talvez não possa lê-lo
Emi

Quando digo "pesquisar" no registro, quero dizer, use os caminhos reais que forneci acima. A menos que alguém saiba melhor, a localização dessas chaves de registro (no registro) não mudará de máquina para máquina. Então você tinha acabado de codificar os caminhos para ver se as chaves existem lá, se assim o que é o valor ...
KHibma

5

[Editar] Ao executar setprogramaticamente (destacado abaixo) o que eu queria, ele pode ser realizado mais facilmente e com um código mais limpo usando Environment.GetEnvironmentVariables () .

Uma opção seria verificar todas as variáveis ​​de ambiente no sistema e tentar provar o seguinte:

1) O valor da variável de ambiente é um diretório? (e se..)

2) Esse diretório contém python.exe?

Consegui fazer isso programaticamente executando o setcomando por meio da .Net Process API . O setcomando, quando usado sem um parâmetro, retorna TODAS as variáveis ​​de ambiente em uso pelo sistema. Para que eu pudesse fazer uma parada, organizar os resultados STDOUT emitidos sete examiná- los para ver se havia alguma coisa disponível (e eu quero dizer QUALQUER COISA ) disponível através do ambiente do sistema python.exe.

A partir desta página discutindo o setcomando:

Digite SET sem parâmetros para exibir todas as variáveis ​​de ambiente atuais.

Para ilustrar, escrevi uma combinação de métodos (e uma classe auxiliar ) que faz o que discuti acima. Eles podem ser otimizados e podem ser utilizados como prova de balas (Try..Catch, etc.), mas se o computador tiver QUALQUER variável de ambiente apontando python.exe, essa abordagem deve ser encontrada! Eu não me importo se o var é chamado PATH, ABBRACADABBRAou o que seja .. se ele aponta python.exe, isso deve ser encontrado.

// C#, you'll need these using statements:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;

Aqui termsestá uma matriz de strings que você transmite à rotina para procurar no nome da variável de ambiente ou em seus nvalores (ou seja, PATHpode ter vários valores, mas a maioria dos outros vars terá apenas um). Verifique se todas as strings termsestão em MAIÚSCULAS!

(Quando testei isso, usei simplesmente "PYTHON", encontrado C:\Python27\python.exeno meu sistema doméstico. Mas você pode estendê-lo facilmente para incluir outra sequência de termos [] se quiser inspecionar melhor o caminho de qualquer python.execandidato retornado --- para exemplo, para ver se eles estavam na bandeja do ArcGIS, etc.)

// Top-level method that organizes everything below..
private void scrapeEnvironmentVariables(string[] terms)
{
    // !! ValueObject !! This is a Helper Class, find it at the bottom..
    List<ValueObject> voList = buildListOfEnvironmentObjects();

    foreach (ValueObject vo in voList)
    {
        bool candidateFound = ObjectMatchesSearchTerms(vo, terms);

        if (candidateFound)
        {    
            string exeCandidate = "";
            foreach (string unlikelyPath in vo.values)
            {
                if (Directory.Exists(unlikelyPath))
                {
                    string unlikelyExe = unlikelyPath + "\\python.exe";
                    if(File.Exists(unlikelyExe))
                        exeCandidate = unlikelyExe;
                }

                if (exeCandidate != "")
                {
                    break;
                    // At this point, exeCandidate is a fully-qualified
                    // path to python.exe..
                }
            }

            // If you only want the first hit, break here..
            // As-is, the code will look for even more matches.
            //if (breakOnFirstHit)
            //    break;
        }
    }
}


// Execute Environment.GetEnvironmentVariables() and organize the 
// key..value pairs into 1:n ValueObjects (see Helper Class below).
private List<ValueObject> buildListOfEnvironmentObjects()
{
    // Return a List of 1:n key..value objects.
    List<ValueObject> voList = new List<ValueObject>();

    IDictionary variableDictionary = Environment.GetEnvironmentVariables();
    foreach (DictionaryEntry entry in variableDictionary)
    {
        // Explode multi-values into a List of values (n).
        List<string> values = new List<string>();
        string[] rawValues = ((string)entry.Value).Split(';');
        foreach (string value in rawValues)
            if (value != "") values.Add(value.ToUpper());

        ValueObject valueObject = new ValueObject();
        valueObject.key = ((string)entry.Key).ToUpper();
        valueObject.values = values.ToArray();

        voList.Add(valueObject);
    }
    return voList;
}


// Compare the key and any value(s) in a ValueObject with all the
// terms submitted to the top-level method. If **ALL** the terms
// match (against any combination of key..value), it returns true.
private bool ObjectMatchesSearchTerms(ValueObject vo, string[] terms)
{
    int matchCount = 0;

    foreach (string term in terms)
    {
        if (vo.key.Contains(term))              // screen the key
            matchCount++;

        foreach (string value in vo.values)     // screen N values
        {
            if (value.Contains(term))
                matchCount++;
        }
    }

    // Test against >= because it's possible the match count could
    // exceed the terms length, like if a match occurred in both the
    // key and the value(s). So >= avoids omiting that possibility.
    return (matchCount >= terms.Length) ? true : false;
}    

E na parte inferior da minha classe principal, incluí a seguinte classe auxiliar :

class ValueObject : Object
{
    public ValueObject() { } // default constructor

    public string key;
    public string[] values;
}

1
Isso é frágil, pois o usuário pode personalizar o diretório de instalação do Python no instalador do ArcGIS Desktop. Além disso, a PYTHONPATHvariável NÃO é a que você deseja.
blah238

@ blah238, às vezes frágil é tudo que você tem. Fiquei realmente surpreso ao ver o Arc ligado ao PYTHONPATH. Essa é uma instalação 9.2 padrão. No entanto, o OP perguntou como acessar programaticamente o ArcGIS python.exe, e a abordagem que eu recomendei, frágil ou não, faz isso.
elrobis

Não posso dizer que entendi o voto negativo, essa resposta é realmente " não útil "? Pode não ser incrível , mas certamente é uma opção, provavelmente funcionaria para uma instalação típica do Arc e, no mínimo, adiciona algo útil ao encadeamento - especificamente, ilustra uma instalação padrão do Arc optando por vincular seu python. exe com uma variável de ambiente diferente de PATH.
elrobis

Desculpe, mas você está incorreto. A variável PYTHONPATH é usada pelo Python para encontrar módulos, não pelo ArcGIS para encontrar o Python. Verifique o link.
blah238

@ blah238, acho que a captura de tela foi esmagadora / obscureceu o argumento que eu estava tentando fazer. (Especificamente, minha sugestão para o OP não pretendia enfatizar PYTHONPATH, que era a única var nesse sistema específico que aponta para trás python.exe.) De qualquer forma, revisei minha resposta para incluir um exemplo de código C # funcional e agradeceria saber se você ainda não concorda com essa abordagem. Obrigado / E.
precisa saber é

4

Gostaria de oferecer uma solução alternativa, com base no meu comentário na pergunta acima. Para um projeto atual, estou fazendo algo muito semelhante; Eu tenho um suplemento .NET que, quando o usuário clica em um botão na interface do ArcMap, um script Python é executado. Eu fiz com que uma variável de ambiente PATH definida como executável do ArcGIS Python fosse um requisito, para que eu realmente não tenha que me preocupar em incluir o caminho para o exe do Python no meu código .NET.

Agora, enquanto em desenvolvimento, os testadores estão apenas configurando a variável PATH manualmente. Mas, eventualmente, terei um instalador do Windows (exe) criado que instalará o suplemento, instalará quaisquer dependências do Python e definirá as variáveis ​​PATH necessárias. Para isso, estou usando o NSIS (Nullsoft Scriptable Install System) , um sistema de código aberto para a criação de instaladores do Windows. Aqui estão alguns dos códigos que desenvolvi até agora, o que é bastante difícil. Basicamente, ele procura no registro para ver se as variáveis ​​PATH de interesse estão lá e, se não estiverem, as adiciona. Tem que ser executado como administrador, é claro.

include "StrFunc.nsh"
!include "LogicLib.nsh"

/*
  Name: VIESORE_Installer.nsi
  Author: Chad Cooper, CAST
  Date: 7/16/2012
  Purpose: NSIS installer script for .exe creation by NSIS. Installs VIESORE components and sets up environment.
*/

Name "VIESORE"
Caption "VIESORE Installer"
Outfile "VIESOREInstaller.exe"

RequestExecutionLevel admin

# Initialize functions
${StrLoc}
# Initialize user variables
Var path

Section "Set SYSTEM PATH environmental variables"
    ReadRegStr $0 HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "Path"
    ${StrLoc} $1 $0 "C:\Python26\ArcGIS10.0" ">"
    ${StrLoc} $2 $0 "C:\Python26\ArcGIS10.0\Scripts" ">"
        ${StrLoc} $3 $0 "C:\Python26\ArcGIS10.0\Lib\site-packages" ">"
        ${StrLoc} $4 $0 "C:\Program Files\e-on software\Vue 10 Infinite\Application" ">"
        # Test to see if env vars exist in current system PATH, if not add them to $path variable
        ${If} $3 == ""
                StrCpy $path "C:\Python26\ArcGIS10.0\Lib\site-packages"
        ${EndIf}
        ${If} $2 == ""
                StrCpy $path "C:\Python26\ArcGIS10.0\Scripts;$path"
        ${EndIf}
        ${If} $1 == ""
                StrCpy $path "C:\Python26\ArcGIS10.0;$path"
        ${EndIf}
        ${If} $4 == ""
                StrCpy $path "C:\Program Files\e-on software\Vue 10 Infinite\Application;$path"
        ${EndIf}
        DetailPrint "$path written to system PATH"
    WriteRegStr HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "Path" "$0;$path"
    ReadRegStr $5 HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "Path"
    DetailPrint "New Path: $5"
SectionEnd

Portanto, novamente, isso não encontra o caminho para o ArcGIS Python exe, mas permite que você dê ao usuário final o poder de configurá-lo de maneira adequada e fácil.


+1 Eu concordo completamente com esta recomendação --- onde, no alto nível, Chad está dizendo "não trabalhe com o problema para trás para deduzir a instância python do Arc, use um instalador e deixe para o SysAdmin estabelecer o python correto" instância." @ChadCooper, o NSIS oferece a você algum controle de interface do usuário para que você possa substituir esses caminhos padrão, se quiser? Não estou vendo isso implícito no código, mas aposto que está lá.
elrobis

@elrobis - Eu aposto que você pode substituir / editar / alterar os existentes - o NSIS é muito configurável e pode criar um instalador bastante elegante - você só precisa descobrir o código para escrevê-lo.
Chad Cooper #

Criar um instalador para um suplemento parece um pouco louco. Além disso, que modificações você precisaria fazer para oferecer suporte a 10.1, 10.2, etc., bem como 10.0?
blah238

@ blah238 - sim, parece loucura, mas meu instalador para esse projeto em particular estará fazendo muito mais, como mencionado anteriormente. Meu suplemento é estritamente para 10.0. Eu acho que para versões diferentes do ArcGIS, você pode verificar o registro para ver qual versão está instalada e agir em conformidade.
Chad Cooper
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.