In January I wrote a post for the Rust 2019 call for blogs. The 2020 call is aiming for an RFC and roadmap earlier this time, so here is my 2020 post =]
#[wasm_bindgen]
but for FFIThis sort of happened... because WebAssembly is growing =]
I was very excited when Interface Types showed up in August, and while it is still very experimental it is moving fast and bringing saner paths for interoperability than raw C FFIs. David Beazley even point this at the end of his PyCon India keynote, talking about how easy is to get information out of a WebAssembly module compared to what had to be done for SWIG.
This doesn't solve the problem where strict C compatibility is required, or for platforms where a WebAssembly runtime is not available, but I think it is a great solution for scientific software (or, at least, for my use cases =]).
I did some of those this year (bbhash-sys and mqf), and also found some great crates to use in my projects. Rust is picking up steam in bioinformatics, being used as the primary choice for high quality software (like varlociraptor, or the many coming from 10X Genomics) but it is still somewhat hard to find more details (I mostly find it on Twitter, and sometime Google Scholar alerts). It would be great to start bringing this info together, which leads to...
Hey, this one happened! Luca Palmieri started a conversation on reddit and the #science-and-ai Discord channel on the Rust community server was born! I think it works pretty well, and Luca also has being doing a great job running workshops and guiding the conversation around rust-ml.
Rust is amazing because it is very good at bringing many concepts and ideas that seem contradictory at first, but can really shine when synthesized. But can we share this combined wisdom and also improve the situation in other places too? Despite the "Rewrite it in Rust" meme, increased interoperability is something that is already driving a lot of the best aspects of Rust:
Interoperability with other languages: as I said before, with WebAssembly (and Rust being having the best toolchain for it) there is a clear route to achieve this, but it will not replace all the software that already exist and can benefit from FFI and C compatibility. Bringing together developers from the many language specific binding generators (helix, neon, rustler, PyO3...) and figuring out what's missing from them (or what is the common parts that can be shared) also seems productive.
Interoperability with new and unexplored domains. I think Rust benefits enormously from not focusing only in one domain, and choosing to prioritize CLI, WebAssembly, Networking and Embedded is a good subset to start tackling problems, but how to guide other domains to also use Rust and come up with new contributors and expose missing pieces of the larger picture?
Another point extremely close to interoperability is training. A great way to interoperate with other languages and domains is having good documentation and material from transitioning into Rust without having to figure everything at once. Rust documentation is already amazing, especially considering the many books published by each working group. But... there is a gap on the transitions, both from understanding the basics of the language and using it, to the progression from beginner to intermediate and expert.
I see good resources for JavaScript and Python developers, but we are still covering a pretty small niche: programmers curious enough to go learn another language, or looking for solutions for problems in their current language.
Can we bring more people into Rust?
RustBridge is obviously the reference here,
but there is space for much,
much more.
Using Rust in The Carpentries lessons?
Creating RustOpenSci
,
mirroring the communities of practice of rOpenSci and pyOpenSci?
Neste tutorial, será abordado o processo de criação de um dict ou dicionário, a partir de um ou mais dicts em Python.
Como já é de costume da linguagem, isso pode ser feito de várias maneiras diferentes.
Pra começar, vamos supor que temos os seguintes dicionários:
dict_1 = {
'a': 1,
'b': 2,
}
dict_2 = {
'b': 3,
'c': 4,
}
Como exemplo, vamos criar um novo dicionário chamado new_dict com os valores de dict_1 e dict_2 logo acima. Uma abordagem bem conhecida é utilizar o método update.
new_dict = {}
new_dcit.update(dict_1)
new_dcit.update(dict_2)
Assim, temos que new_dict será:
>> print(new_dict)
{
'a': 1,
'b': 3,
'c': 4,
}
Este método funciona bem, porém temos de chamar o método update para cada dict que desejamos mesclar em new_dict. Não seria interessante se fosse possível passar todos os dicts necessários já na inicialização de new_dict?
O Python 3 introduziu uma maneira bem interessante de se fazer isso, utilizando os operadores **
.
new_dict = {
**dict_1,
**dict_2,
}
Assim, de maneira semelhante ao exemplo anterior, temos que new_dict será :
>> print(new_dict['a'])
1
>> print(new_dict['b'])
3
>> print(new_dict['c'])
4
Ao utilizamos o procedimento de inicialização acima, devemos tomar conseiderar alguns fatores. Apenas os valores do primeiro nível serão realmente duplicados no novo dicionário. Como exemplo, vamos alterar uma chave presente em ambos os dicts e verificar se as mesmas possuem o mesmo valor:
>> dict_1['a'] = 10
>> new_dict['a'] = 11
>> print(dict_1['a'])
10
>> print(new_dict['a'])
11
Porém isso muda quando um dos valores de dict_1 for uma list, outro dict ou algum objeto complexo. Por exemplo:
dict_3 = {
'a': 1,
'b': 2,
'c': {
'd': 5,
}
}
e agora, vamos criar um novo dict a partir desse:
new_dict = {
**dict_3,
}
Como no exemplo anterior, podemos imaginar que foi realizado uma cópia de todos os elementos de dict_3, porém isso não é totalmente verdade. O que realmente aconteceu é que foi feita uma cópia superficial dos valores de dict_3, ou seja, apenas os valores de primeiro nível foram duplicados. Observe o que acontece quando alteramos o valor do dict presente na chave c.
>> new_dict['c']['d'] = 11
>> print(new_dict['c']['d'])
11
>> print(dict_3['c']['d'])
11
# valor anterior era 5
No caso da chave c, ela contem uma referência para outra estrutura de dados (um dict, no caso). Quando alteramos algum valor de dict_3['c'], isso reflete em todos os dict que foram inicializados com dict_3. Em outras palavras, deve-se ter cuidado ao inicializar um dict a partir de outros dicts quando os mesmos possuírem valores complexos, como list, dict ou outros objetos (os atributos deste objeto não serão duplicados).
De modo a contornar este inconveniente, podemos utilizar o método deepcopy da lib nativa copy. Agora, ao inicializarmos new_dict:
import copy
dict_3 = {
'a': 1,
'b': 2,
'c': {
'd': 5,
}
}
new_dict = copy.deepcopy(dict_3)
O método deepcopy realiza uma cópia recursiva de cada elemento de dict_3, resolvendo nosso problema. Veja mais um exemplo:
>> new_dict['c']['d'] = 11
>> print(new_dict['c']['d'])
11
>> print(dict_3['c']['d'])
5
# valor não foi alterado
Este artigo tenta demonstrar de maneira simples a criação de dicts, utilizando os diversos recursos que a linguagem oferece bem como os prós e contras de cada abordagem.
Para mais detalhes e outros exemplos, deem uma olhada neste post do forum da Python Brasil aqui.
É isso pessoal. Obrigado por ler!
Eu e o prof. Leonardo Cruz da Faculdade de Ciências Sociais estamos juntos trabalhando no desenvolvimento do Laboratório Amazônico de Estudos Sociotécnicos da UFPA.
Nossa proposta é realizar leituras e debates críticos sobre o tema da sociologia da tecnologia, produzir pesquisas teóricas e empíricas na região amazônica sobre as relações entre tecnologia e sociedade, e trabalhar com tecnologias livres em comunidades próximas a Belém.
No momento estamos com um grupo de estudos montado com cronograma de textos e filmes para trabalharmos e debatermos criticamente. Esse grupo será o embrião para a orientação de alunos de graduação e pós em temas como impacto da inteligência artificial, computação e guerra, cibernética, vigilantismo, capitalismo de plataforma, fake news, pirataria, software livre, e outros.
Aos interessados, nosso cronograma de estudos está disponível nesse link.
E para quem usa Telegram, pode acessar o grupo de discussão aqui.
Quaisquer dúvidas, só entrar em contato!
Este tutorial é baseado no Intro to Django que fica na parte de baixo da página start do Django project.
Até a data deste post o Django está na versão 2.2.2, e requer Python 3.
Python 3.6 ou superior, pip e virtualenv.
Considere que você tenha instalado Python 3.6 ou superior, pip e virtualenv.
Crie uma pasta com o nome django2-pythonclub
$ mkdir django2-pythonclub
$ cd django2-pythonclub
A partir de agora vamos considerar esta como a nossa pasta principal.
Considerando que você está usando Python 3, digite
python3 -m venv .venv
Lembre-se de colocar esta pasta no seu .gitignore
, caso esteja usando.
echo .venv >> .gitignore
Depois ative o ambiente digitando
source .venv/bin/activate
Lembre-se, sempre quando você for mexer no projeto, tenha certeza de ter ativado o
virtualenv
, executando o comandosource .venv/bin/activate
. Você deve repetir esse comando toda a vez que você abrir um novo terminal.
Basta digitar
pip install django==2.2.2
Dica: se você digitar pip freeze
você verá a versão dos programas instalados.
É recomendável que você atualize a versão do pip
pip install -U pip
Se der erro então faça:
python -m pip install --upgrade pip
Eu gosto de usar o django-extensions e o django-widget-tweaks, então digite
pip install django-extensions django-widget-tweaks python-decouple
Importante: você precisa criar um arquivo requirements.txt
para instalações futuras do projeto em outro lugar.
pip freeze > requirements.txt
Este é o resultado do meu até o dia deste post:
(.venv):$ cat requirements.txt
django-extensions==2.1.6
django-widget-tweaks==1.4.3
python-decouple==3.1
pytz==2018.9
six==1.12.0
É muito importante que você não deixe sua SECRET_KEY exposta. Então remova-o imediatamente do seu settings.py ANTES mesmo do primeiro commit. Espero que você esteja usando Git.
Vamos usar o python-decouple escrito por Henrique Bastos para gerenciar nossas variáveis de ambiente. Repare que já instalamos ele logo acima.
Em seguida você vai precisar criar um arquivo .env
, para isso rode o comando a seguir, ele vai criar uma pasta contrib e dentro dele colocar um arquivo env_gen.py
if [ ! -d contrib ]; then mkdir contrib; fi; git clone https://gist.github.com/22626de522f5c045bc63acdb8fe67b24.git contrib/
rm -rf contrib/.git/ # remova a pasta .git que está dentro de contrib.
Em seguida rode
python contrib/env_gen.py
que ele vai criar o arquivo .env
.
Supondo que você está versionando seu código com Git, é importante que você escreva isso dentro do seu arquivo .gitignore
, faça direto pelo terminal
echo .env >> .gitignore
echo .venv >> .gitignore
echo '*.sqlite3' >> .gitignore
Pronto, agora você pode dar o primeiro commit.
Para criar o projeto digite
$ django-admin startproject myproject .
repare no ponto no final do comando, isto permite que o arquivo manage.py
fique nesta mesma pasta django2-pythonclub .
Agora vamos criar a app bands, mas vamos deixar esta app dentro da pasta myproject. Então entre na pasta
$ cd myproject
e digite
$ python ../manage.py startapp bands
A intenção é que os arquivos tenham a seguinte hierarquia nas pastas:
.
├── manage.py
├── myproject
│ ├── bands
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── models.py
│ │ ├── tests.py
│ │ └── views.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── requirements.txt
Agora permaneça sempre na pasta django2-pythonclub
cd ..
e digite
$ python manage.py migrate
para criar a primeira migração (isto cria o banco de dados SQLite), e depois rode a aplicação com
$ python manage.py runserver
e veja que a aplicação já está funcionando. Veja o endereço da url aqui
Django version 2.2.2, using settings 'myproject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Em INSTALLED_APPS
acrescente as linhas abaixo.
INSTALLED_APPS = (
...
'widget_tweaks',
'django_extensions',
'myproject.bands',
)
E mude também o idioma.
LANGUAGE_CODE = 'pt-br'
E caso você queira o mesmo horário de Brasília-BR
TIME_ZONE = 'America/Sao_Paulo'
Já que falamos do python-decouple, precisamos de mais alguns ajustes
from decouple import config, Csv
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv())
Veja que é importante manter sua SECRET_KEY bem guardada (em outro lugar).
Então crie um arquivo .env
e guarde sua SECRET_KEY dentro dele, exemplo:
SECRET_KEY=your_secret_key
DEBUG=True
ALLOWED_HOSTS=127.0.0.1,.localhost
from django.db import models
from django.urls import reverse_lazy
class Band(models.Model):
"""A model of a rock band."""
name = models.CharField(max_length=200)
can_rock = models.BooleanField(default=True)
class Meta:
ordering = ('name',)
verbose_name = 'band'
verbose_name_plural = 'bands'
def __str__(self):
return self.name
def get_absolute_url(self):
# retorna a url no formato /bands/1/
return reverse_lazy('band_detail', kwargs={'pk': self.pk})
def get_members_count(self):
# count members by band
# conta os membros por banda
return self.band.count()
class Member(models.Model):
"""A model of a rock band member."""
name = models.CharField("Member's name", max_length=200)
instrument = models.CharField(choices=(
('g', "Guitar"),
('b', "Bass"),
('d', "Drums"),
('v', "Vocal"),
('p', "Piano"),
),
max_length=1
)
band = models.ForeignKey("Band", related_name='band', on_delete=models.CASCADE)
class Meta:
ordering = ('name',)
verbose_name = 'member'
verbose_name_plural = 'members'
def __str__(self):
return self.name
Tem algumas coisas que eu não estou explicando aqui para o tutorial ficar curto, mas uma coisa importante é que, como nós editamos o models.py vamos precisar criar um arquivo de migração do novo modelo. Para isso digite
python manage.py makemigrations
python manage.py migrate
O primeiro comando cria o arquivo de migração e o segundo o executa, criando as tabelas no banco de dados.
from django.urls import include, path
from myproject.bands import views as v
from django.contrib import admin
app_name = 'bands'
urlpatterns = [
path('', v.home, name='home'),
# path('bands/', v.band_list, name='bands'),
# path('bands/<int:pk>/', v.band_detail, name='band_detail'),
# path('bandform/', v.BandCreate.as_view(), name='band_form'),
# path('memberform/', v.MemberCreate.as_view(), name='member_form'),
# path('contact/', v.band_contact, name='contact'),
# path('protected/', v.protected_view, name='protected'),
# path('accounts/login/', v.message),
path('admin/', admin.site.urls),
]
Obs: deixei as demais urls comentada porque precisa da função em views.py para que cada url funcione. Descomente cada url somente depois que você tiver definido a função em classe em views.py a seguir.
from django.shortcuts import render
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
from django.views.generic import CreateView
from django.urls import reverse_lazy
from .models import Band, Member
# from .forms import BandContactForm, BandForm, MemberForm
Obs: Deixei a última linha comentada porque ainda não chegamos em forms.
A função a seguir retorna um HttpResponse, ou seja, uma mensagem simples no navegador.
def home(request):
return HttpResponse('Welcome to the site!')
A próxima função (use uma ou outra) renderiza um template, uma página html no navegador.
def home(request):
return render(request, 'home.html')
A função band_list
retorna todas as bandas.
Para fazer a busca por nome de banda usamos o comando search = request.GET.get('search_box')
, onde search_box
é o nome do campo no template band_list.html.
E os nomes são retornados a partir do comando bands = bands.filter(name__icontains=search)
. Onde icontains
procura um texto que contém a palavra, ou seja, você pode digitar o nome incompleto (ignora maiúsculo ou minúsculo).
def band_list(request):
""" A view of all bands. """
bands = Band.objects.all()
search = request.GET.get('search_box')
if search:
bands = bands.filter(name__icontains=search)
return render(request, 'bands/band_list.html', {'bands': bands})
Em urls.py pode descomentar a linha a seguir:
path('bands/', v.band_list, name='bands'),
A função band_contact
mostra como tratar um formulário na view. Esta função requer BandContactForm
, explicado em forms.py.
def band_contact(request):
""" A example of form """
if request.method == 'POST':
form = BandContactForm(request.POST)
else:
form = BandContactForm()
return render(request, 'bands/band_contact.html', {'form': form})
Em urls.py pode descomentar a linha a seguir:
path('contact/', v.band_contact, name='contact'),
A função band_detail
retorna todos os membros de cada banda, usando o pk
da banda junto com o comando filter
em members.
def band_detail(request, pk):
""" A view of all members by bands. """
band = Band.objects.get(pk=pk)
members = Member.objects.all().filter(band=band)
context = {'members': members, 'band': band}
return render(request, 'bands/band_detail.html', context)
Em urls.py pode descomentar a linha a seguir:
path('bands/<int:pk>/', v.band_detail, name='band_detail'),
BandCreate
e MemberCreate
usam o Class Based View para tratar formulário de uma forma mais simplificada usando a classe CreateView
. O reverse_lazy
serve para tratar a url de retorno de página.
As classes a seguir requerem BandForm
e MemberForm
, explicado em forms.py.
class BandCreate(CreateView):
model = Band
form_class = BandForm
template_name = 'bands/band_form.html'
success_url = reverse_lazy('bands')
class MemberCreate(CreateView):
model = Member
form_class = MemberForm
template_name = 'bands/member_form.html'
success_url = reverse_lazy('bands')
Em urls.py pode descomentar a linha a seguir:
path('bandform/', v.BandCreate.as_view(), name='band_form'),
path('memberform/', v.MemberCreate.as_view(), name='member_form'),
A próxima função requer que você entre numa página somente quando estiver logado.
[@login_required](https://docs.djangoproject.com/en/2.2/topics/auth/default/#the-login-required-decorator)
é um decorator.
login_url='/accounts/login/'
é página de erro, ou seja, quando o usuário não conseguiu logar.
E render(request, 'bands/protected.html',...
é página de sucesso.
@login_required(login_url='/accounts/login/')
def protected_view(request):
""" A view that can only be accessed by logged-in users """
return render(request, 'bands/protected.html', {'current_user': request.user})
HttpResponse
retorna uma mensagem simples no navegador sem a necessidade de um template.
def message(request):
""" Message if is not authenticated. Simple view! """
return HttpResponse('Access denied!')
Em urls.py pode descomentar a linha a seguir:
path('protected/', v.protected_view, name='protected'),
path('accounts/login/', v.message),
Para criar novas migrações com base nas alterações feitas nos seus modelos
$ python manage.py makemigrations bands
Obs: talvez dê erro porque está faltando coisas de forms.py, explicado mais abaixo.
Para aplicar as migrações
$ python manage.py migrate
Para criar um usuário e senha para o admin
$ python manage.py createsuperuser
Para rodar a aplicação localmente
$ python manage.py runserver
Após criar um super usuário você pode entrar em localhost:8000/admin
Obs: Se você entrar agora em localhost:8000 vai faltar o template home.html. Explicado mais abaixo.
É o interpretador interativo do python rodando via terminal direto na aplicação do django.
Com o comando a seguir abrimos o shell do Django.
$ python manage.py shell
Mas se você está usando o django-extensions (mostrei como configurá-lo no settings.py), então basta digitar
$ python manage.py shell_plus
Veja a seguir como inserir dados direto pelo shell.
>>> from myproject.bands.models import Band, Member
>>> # Com django-extensions não precisa fazer o import
>>> # criando o objeto e salvando
>>> band = Band.objects.create(name='Metallica')
>>> band.name
>>> band.can_rock
>>> band.id
>>> # criando uma instancia da banda a partir do id
>>> b = Band.objects.get(id=band.id)
>>> # criando uma instancia do Membro e associando o id da banda a ela
>>> m = Member(name='James Hetfield', instrument='b', band=b)
>>> m.name
>>> # retornando o instrumento
>>> m.instrument
>>> m.get_instrument_display()
>>> m.band
>>> # salvando
>>> m.save()
>>> # listando todas as bandas
>>> Band.objects.all()
>>> # listando todos os membros
>>> Member.objects.all()
>>> # criando mais uma banda
>>> band = Band.objects.create(name='The Beatles')
>>> band = Band.objects.get(name='The Beatles')
>>> band.id
>>> b = Band.objects.get(id=band.id)
>>> # criando mais um membro
>>> m = Member(name='John Lennon', instrument='v', band=b)
>>> m.save()
>>> # listando tudo novamente
>>> Band.objects.all()
>>> Member.objects.all()
>>> exit()
Você pode criar os templates com os comandos a seguir...
$ mkdir -p myproject/bands/templates/bands
$ touch myproject/bands/templates/{menu,base,home}.html
$ touch myproject/bands/templates/bands/{band_list,band_detail,band_form,band_contact,member_form,protected}.html
... ou pegar os templates já prontos direto do Github.
mkdir -p myproject/bands/templates/bands
wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/base.html -P myproject/bands/templates/
wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/home.html -P myproject/bands/templates/
wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/menu.html -P myproject/bands/templates/
wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/band_contact.html -P myproject/bands/templates/bands/
wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/band_detail.html -P myproject/bands/templates/bands/
wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/band_form.html -P myproject/bands/templates/bands/
wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/band_list.html -P myproject/bands/templates/bands/
wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/member_form.html -P myproject/bands/templates/bands/
wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/protected.html -P myproject/bands/templates/bands/
$ touch myproject/bands/forms.py
Edite o forms.py.
from django import forms
from .models import Band, Member
class BandContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField(widget=forms.Textarea)
sender = forms.EmailField()
cc_myself = forms.BooleanField(required=False)
class BandForm(forms.ModelForm):
class Meta:
model = Band
fields = '__all__'
class MemberForm(forms.ModelForm):
class Meta:
model = Member
fields = '__all__'
Lembra que eu deixei o código comentado em views.py?
Descomente ele por favor
from .forms import BandContactForm, BandForm, MemberForm
Criamos uma customização para o admin onde em members aparece um filtro por bandas.
from django.contrib import admin
from .models import Band, Member
class MemberAdmin(admin.ModelAdmin):
"""Customize the look of the auto-generated admin for the Member model."""
list_display = ('name', 'instrument')
list_filter = ('band',)
admin.site.register(Band) # Use the default options
admin.site.register(Member, MemberAdmin) # Use the customized options
Vamos baixar alguns arquivos para criar os dados no banco a partir de um CSV.
wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/create_data.py
mkdir fix
wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/fix/bands.csv -P fix/
wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/fix/members.csv -P fix/
Estando na pasta principal, rode o comando
python create_data.py
que ele vai carregar alguns dados pra você.
Veja o código de create_data.py.
Veja o código completo em https://github.com/rg3915/django2-pythonclub
git clone https://github.com/rg3915/django2-pythonclub.git
Often when starting a new Python project we need to spend some time thinking about how to manage the settings, decide on which module the configuration manager will be written, decide which name to give to this module, create a class or function to store the configuration keys, create the conditions for multiple environments and still need to worry about where these keys will be stored and in which file format?
No more! now you have Dynaconf!
Spend your precious time developing your application, run pip install dynaconf
and let Dynaconf take care of your settings.
from dynaconf import settings
And that's it!
That is the only line of code you need, no complicated boilerplate, no hadouken-ifs, no need to maintain config classes.
You must be wondering - "What magic is this? Where does the setting values come from?"
Well, there is no magic, and the values can come from wherever you want, by default and following the recommendations of the 12 factor apps Dynaconf has preference for environment variables.
# optionally you can save it in .env file
export DYNACONF_DEBUG=true
export DYNACONF_NAME=Bruno
# app.py
from dynaconf import settings
if settings.DEBUG is True:
print(settings.NAME)
$ python3 app.py
Bruno
And the environment variables for Dynaconf are typed using the
toml
format sotrue
has been evaluated to booleanTrue
and this makes it possible to export lists, dictionaries, floats, booleans, and so on.
Well, that's cool, but your project will not have settings coming from just the environment variables, I'm sure you want to have a settings file where you can set default values.
Dynaconf can read multiple file formats, out of the box it supports .py
, .toml
, .ini
and .json
. If PyYAML
is installed then it will also support .yaml
and you don't have to take care of finding and opening the files. The preferred format is .toml
because it is currently the best configuration format, widely addopted, and you can use whatever file format you want.
# settings.toml
[default]
name = "Bruno"
[development]
debug = true
[production]
debug = false
# app.py
from dynaconf import settings
if settings.DEBUG is True:
print(settings.NAME)
$ python3 app.py
Bruno
And as you can see now using settings.
file we can have separate [environments]
by default dynaconf will always work on [development]
which means only [default]
and [development]
variables will be loaded. At any time you can do export ENV_FOR_DYNACONF=production
and then it starts using the values from [production]
environment.
If you don't want to have that separation by environment, you can simply put everything under
[default]
section.
Read more about environments and settings file
A good practice is to not store your secrets like passwords and tokens directly on settings files, because you can make a mistake and commit that to a public git repository, so there are some alternatives to store secrets
Not recommended
There are some people who disagrees and it is really a point of security failure. However, if you are sure that your machine is protected, you can leave the secrets in the variables, at your own risk, Dynaconf can read it normally.
This is a simple level of security for keeping secrets, and it is specially useful to keep development
secrets. That token you use to access the development API etc.
It is very simple, together with your normal settings.toml
you put a new file called .secrets.toml
and store your sensitive data there. Dynaconf will read it after the read of the settings.toml
Wait.. how does it solve my security problem?
Well it does not (yet) but it make your life easier in 2 ways.
.secrets.*
in your ~/.gitignore
so you will never commit the mistake of sending that data to a public git repository.DEBUG_LEVEL_FOR_DYNACON=DEBUG
is exported, all loaded values are printed but if the values comes from a .secrets.*
file, then only the key is printed and the value is masked. It is useful to use on public CI.You can also tell Dynaconf to load that file from somewhere else export SECRETS_FOR_DYNACONF=/path/to/secrets/location/.secrets.yaml
(very useful for CI like Jenkins)
Recommended!
Now we are really talking about true security
Using Vault is the better way to protect your secrets dynaconf has built-in support:
export VAULT_ENABLED_FOR_DYNACONF=true
export VAULT_URL_FOR_DYNACONF=https://..../
export OTHER_VAULT_CONFIGS
And then if have for example the TOKEN
stores on your vault server you can simply do:
from dynaconf import settings
perform_your_authentication(settings.TOKEN)
Vault has lots of features like leases and sealed vaults.
Dynaconf provides extensions for those 2 frameworks, with 2 lines of code you enable it and then your framework will start reading settings from Dynaconf.
# settings.py
import dynaconf # noqa
settings = dynaconf.DjangoDynaconf(__name__, **options)
Now you if you do export DJANGO_FOO=BAR
you can access inside your app via django.conf.settings.FOO
# app.py
from dynaconf import FlaskDynaconf
FlaskDynaconf(app, **options)
Now you if you do export FLASK_FOO=BAR
you can access inside your app via app.config['FOO']
You can extend Dynaconf adding new loaders!
Dynaconf already provides loaders for:
.py
.json
.yaml
.toml
.ini
.env
filesBut if this is not a fit for your project you can still create your own loader
Dynaconf is the only thing you need to manage your settings!
.secrets
file or vault server
..env
files to automate the export of environment variables.app.config
object.conf.settings
object.Settings are simple but Dynaconf provides even more features like Feature Flags, Settings Context Managers, Plugin Settings etc..
Documentation: http://dynaconf.readthedocs.io/
Dynaconf is waiting for your feedback and Contribution
:)
from dynaconf import settings
settings.THANKS_FOR_READING
Criei uma espécie de desafio mental, esse desafio consiste em resolver problemas já enfrentados ou de outras origens de forma criativa, em pouco tempo e se possível evolvendo pouco ou nenhum custo!
E o desafio da semana foi criar um daqueles projetos de “media indoor”.
Então pensei: “já sei vou usar grpc, s3 rsync, elastic transcoder, fastly, frontend do admin em react e é claro kubernetes!”
Já no cliente, que é responsável por tocar os vídeos…: “vou escrever em Qt, criar a interface em QML, usar boost.asio para o meu downloader e criar uma distribuição de Linux embarcado usando o yocto !“.
“The best code is no code at all.”
As pessoas, e desse ramo principalmente, estão acostumadas a usar planilhas para as mais variadas tarefas, então por que não usar uma planilha como “admin”?
Ao invés de desenvolver um complexo sistema de gerenciamento de arquivos de vídeo, com transcodificação usando as ferramentas que citei acima, usando lambda e outras coisas, vamos usar o YouTube.
Quê?
É isso mesmo, no nosso protótipo vamos usar o YouTube, pois não tem nenhum custo e já faz a conversão e distribuição do vídeo nos mais variados formatos e tamanhos.
Atenção: De acordo com os termos de uso do YouTube não é permitido reproduzir o conteúdo da plataforma fora do player do youtube, o que estou demonstrando é apenas para fins educacionais.
Nada mais do que um pequeno script em bash será necessário para executar as tarefas de baixar a playlist, os vídeos, a remoção de vídeos não mais usados entre outras coisas.
Já o player propriamente dito é o omxplayer, que é capaz de decodificar vídeos usando aceleração por hardware; omxplayer
foi escrito especialmente para a GPU do Raspberry Pi e faz uso da API OpenMAX, é extremamente eficiente.
O trecho abaixo é de um apps script que transforma a primeira coluna da planilha num array de objetos e serializa a reposta num JSON.
function doGet(request) {
var app = SpreadsheetApp;
var worksheet = app.getActiveSpreadsheet();
var sheet = worksheet.getSheetByName(request.parameters.sheet);
if (sheet) {
var range = sheet.getDataRange();
var values = range.getValues();
var headers = values.shift();
var playlist = values.map(function (el) { return { url: el[0] }; });
return ContentService.createTextOutput(JSON.stringify({ playlist: playlist }))
.setMimeType(ContentService.MimeType.JSON);
}
}
É possível publicar o script acima num endereço público e de acesso anônimo, de modo que seja possível baixar o JSON até mesmo pelo cURL, e é com essa reposta que iremos usar para saber quais arquivos baixar e gerar a playlist:
$ curl -sL "https://script.google.com/macros/s/${...}/exec?sheet=Sheet1" | jq
{
"result": [
{
"url": "https://www.youtube.com/watch?v=..."
},
...
]
}
Com uma simples entrada no cron
é possível criar um agendamento para baixar a playlist de tempos em tempos:
*/30 * * * * user curl -sL "https://script.google.com/macros/s/${...}/exec?sheet=Sheet1" > playlists/1.json
A função download
usa a ferramenta jq para gerar uma lista de urls a serem baixadas pelo youtube-dl que por sua vez executa um pequeno script (--exec
) para adicionar o arquivo recém baixado para a playlist, tomando cuidado para não duplicar:
download() {
cat playlists/*.json | jq '.[].url' \
| xargs youtube-dl --exec "grep -sqxF '{}' $playlist || echo '{}' >> $playlist"
}
Alguns parâmetros do
youtube-dl
foram omitidos pois foram movidos para o arquivo de configuração global.
Com o entr é possível monitorar se algum arquivo foi modificado ou mesmo adicionado novos arquivos no diretório; se isso acontecer, a função download
será chamada:
watch() {
while :; do
ls -d playlists/*.json | entr -d "$0" download
done
}
De tempos em tempos é necessário remover os arquivos antigos e downloads incompletos; a função recycle
remove todos os arquivos do tipo vídeo modificados pela última vez há mais de 7 dias e que não estão sendo usados:
A razão de ser alguns dias depois e não imediatamente é de ser maleável caso tenha sido algum equívoco.
recycle() {
declare -a args=(
-mtime +7
-type f
)
while read -r uri; do
args+=(-not -name "$uri")
done <<< "$(cat $playlist)"
find "$PWD" "${args[@]}" -exec bash -c "file -b --mime-type {} | grep -q ^video/" \; -delete
}
Todas essas funções podem ser chamadas inúmeras vezes sem efeitos indesejados.
Tocar a playlist é a parte mais fácil:
play() {
setterm -cursor off
export DISPLAY=":0.0"
while :; do
while read -r uri; do
omxplayer --refresh --no-osd --no-keys "$uri"
xrefresh -display :0
done <<< "$(cat $playlist)"
done
}
Graças ao
omxplayer
o consumo de CPU fica bem baixo, mesmo em 1080@60fps, algo em torno de ~0.5% num Raspberry 3.
O próximo passo é contabilizar algumas estatísticas, como o número de vezes que um vídeo foi tocado, se teve alguma interrupção por falta de energia ou por problemas técnicos, etc.
Para isso uma boa pedida é o papertrail, com essa ferramenta é possível centralizar todos os logs da máquina, exportar para o BigQuery e executar as consultas na mesma planilha que ficam os vídeos.
Ops… Acho que minha febre por cloud computing voltou :-)
por Rodrigo Delduca (rodrigodelduca@gmail.com) em 10 de January de 2019 às 00:00
The Rust community requested feedback last year for where the language should go in 2018, and now they are running it again for 2019. Last year I was too new in Rust to organize a blog post, but after an year using it I feel more comfortable writing this!
(Check my previous post about replacing the C++ core in sourmash with Rust for more details on how I spend my year in Rust).
Anything that involves doing science using computers counts as scientific programming. It includes from embedded software running on satellites to climate models running in supercomputers, from shell scripts running tools in a pipeline to data analysis using notebooks.
It also makes the discussion harder, because it's too general! But it is very important to keep in mind, because scientists are not your regular user: they are highly qualified in their field of expertise, and they are also pushing the boundaries of what we know (and this might need flexibility in their tools).
In this post I will be focusing more in two areas: array computing (what most people consider 'scientific programming' to be) and "data structures".
This one is booming in the last couple of years due to industry interest in data sciences and deep learning (where they will talk about tensors instead of arrays), and has its roots in models running in supercomputers (a field where Fortran is still king!). Data tends to be quite regular (representable with matrices) and amenable to parallel processing.
A good example is the SciPy stack in Python, built on top of NumPy and SciPy. The adoption of the SciPy stack (both in academia and industry) is staggering, and many alternative implementations try to provide a NumPy-like API to try to capture its mindshare.
This is the compute-intensive side science (be it CPU or GPU/TPU), and also the kind of data that pushed CPU evolution and is still very important in defining policy in scientific computing funding (see countries competing for the largest supercomputers and measuring performance in floating point operations per second).
For data that is not so regular the situation is a bit different. I'll use bioinformatics as an example: the data we get out of nucleotide sequencers is usually represented by long strings (of ACGT), and algorithms will do a lot of string processing (be it building string-overlap graphs for assembly, or searching for substrings in large collections). This is only one example: there are many analyses that will work with other types of data, and most of them don't have a universal data representation as in the array computing case.
This is the memory-intensive science, and it's hard to measure performance in floating point operations because... most of the time you're not even using floating point numbers. It also suffers from limited data locality (which is almost a prerequisite for compute-intensive performance).
There is something common in both cases: while performance-intensive code is implemented in C/C++/Fortran, users usually interact with the API from other languages (especially Python or R) because it's faster to iterate and explore the data, and many of the tools already available in these languages are very helpful for these tasks (think Jupyter/pandas or RStudio/tidyverse). These languages are used to define the computation, but it is a lower-level core library that drives it (NumPy or Tensorflow follow this idea, for example).
The biggest barrier to learning Rust is the ownership model, and while we can agree it is an important feature it is also quite daunting for newcomers, especially if they don't have previous programming experience and exposure to what bugs are being prevented. I don't see it being the first language we teach to scientists any time soon, because the majority of scientists are not system programmers, and have very different expectations for a programming language. That doesn't mean that they can't benefit from Rust!
Rust is already great for building the performance-intensive parts, and thanks to Cargo it is also a better alternative for sharing this code around, since they tend to get 'stuck' inside Python or R packages. And the 'easy' approach of vendoring C/C++ instead of having packages make it hard to keep track of changes and doesn't encourage reusable code.
And, of course, if this code is Rust instead of C/C++ it also means that Rust users can use them directly, without depending on the other languages. Seems like a good way to bootstrap a scientific community in Rust =]
#[wasm_bindgen]
but for FFIWhile FFI is an integral part of Rust goals (interoperability with C/C++), I have serious envy of the structure and tooling developed for WebAssembly! (Even more now that it works in stable too)
We already have #[no_mangle]
and pub extern "C"
, but they are quite
low-level. I would love to see something closer to what wasm-bindgen does,
and define some traits (like IntoWasmAbi
) to make it easier to
pass more complex data types through the FFI.
I know it's not that simple, and there are different design restrictions than WebAssembly to take into account... The point here is not having the perfect solution for all use cases, but something that serves as an entry point and helps to deal with the complexity while you're still figuring out all the quirks and traps of FFI. You can still fallback and have more control using the lower-level options when the need rises.
There are new projects bringing more interoperability to dataframes and tensors. While this ship has already sailed and they are implemented in C/C++, it would be great to be a first-class citizen, and not reinvent the wheel. (Note: the arrow project already have pretty good Rust support!)
In my own corner (bioinformatics), the Rust-bio community is doing a great job of wrapping useful libraries in C/C++ and exposing them to Rust (and also a shout-out to 10X Genomics for doing this work for other tools while also contributing to Rust-bio!).
We already have great examples like finch and yacrd, since Rust is great for single binary distribution of programs. And with bioinformatics focusing so much in independent tools chained together in workflows, I think we can start convincing people to try it out =]
Another idea is to draw inspiration from rOpenSci and have a Rust equivalent, where people can get feedback about their projects and how to better integrate it with other crates. This is quite close to the working group idea, but I think it would serve more as a gateway to other groups, more focused on developing entry-level docs and bringing more scientists to the community.
In the end, I feel like this post ended up turning into my 'wishful TODO list' for 2019, but I would love to find more people sharing these goals (or willing to take any of this and just run with it, I do have a PhD to finish! =P)
Em outubro de 2018, Florianópolis foi sede da sexta edição do LaKademy, o sprint latinoamericano do KDE. Esse momento é uma oportunidade para termos em um mesmo lugar vários desenvolvedores do KDE – tanto veteranos quanto novatos – de diferentes projetos para melhorarem os respectivos softwares em que trabalham e também planejar as ações de promoção da comunidade para o subcontinente.
Na parte técnica, eu trabalhei com Cantor, Sprat, e nos sites do KDE Brasil e LaKademy.
Para o Cantor, eu pesquisei algumas novas maneiras de implementar os backends, em especial quanto a utilização de websockets. Essa é uma ideia antiga que tenta encontrar uma abordagem a ser recomendada para implementação desses backends de forma que possam ser utilizados para qualquer linguagem de programação e funcionem em qualquer plataforma. Entretanto, como nas tentativas anteriores, ainda tenho dúvidas se essa é uma maneira interessante e se os objetivos podem ser atingidos por ela. Enfim, é algo que precisa de mais pesquisa.
Sprat é um editor de texto direcionado para a escrita de artigos científicos. O software implementa a metodologia Amadeus para escrita de artigos, e é mais como uma coleção de sentenças comuns que podem ser utilizadas in seções específicas do artigo. Sprat é meu projeto-pet, e espero lançar esse ano além de transformá-lo em um projeto do KDE.
O site do KDE Brasil uma uma antiga versão do Drupal. Eu e Fred pesquisamos alguns plugins para importar o conteúdo desse site para o WordPress, e atualmente estamos estudando como realizar essa tarefa.
Por último, portei o site do LaKademy para Jekyll. Isso é algo que ainda precisa de algum trabalho, e espero lançá-lo o quanto antes.
Na parte social, discutimos algumas atividades para o KDE nesse ano, como voltar a participar do FISL e Latinoware, tentar comparecer a outros eventos na América Latina (DebConf e Cubaconf, estamos olhando pra vocês), organizar o “Café com Qt” (nosso evento distribuído sobre Qt e KDE), feedbacks sobre o gerenciamento do nosso grupo do KDE Brasil no Telegram, nossos novos materiais promocionais para serem produzidos e distribuídos nos eventos daqui, e mais.
Ainda nesse aspecto, também ajudei os novatos no trabalho de revisão de código do KDE e tirando dúvidas sobre Qt.
LaKademy é uma grande oportunidade para encontrar outros desenvolvedores do KDE e trabalharmos juntos para aumentar nossa comunidade. Nos últimos anos, o KDE assumiu um papel de importância na comunidade brasileira, e estamos planejando expandi-la para diferentes países. Esperamos organizar o próximo LaKademy em algum outro país fora do Brasil, e trabalharemos forte para expandir a comunidade no subcontinente.
Nos vemos no LaKademy 2019!TL;DR Usar o SQLite ao invés do sistema de arquivos para armazenar em forma de blob os assets do jogo pode ser uma ótima ideia.
Dia desses estava lendo SQLite As An Application File Format o que me fez lembrar de quando eu lia muito a respeito de desenvolvimento de jogos, até cheguei a desenvolver um framework chamado Wintermoon, no meu framework eu usei o PhysicsFS foi quando descobri o MPQ e fiquei encantado.
O MPQ é (ou era) amplamente utilizado em praticamente todos os jogos da Blizzard, desde o Diablo (1997) até o StarCraft 2 (2010). Inclusive o StarCraft 2 recebe atualizações até hoje, e quase que mensalmente desde seu lançamento! Digo isto para dar um contexto de onde quero chegar.
O MPQ leva o nome de seu criador, e surgiu devido há alguns requerimentos da época, como segurança, acesso rápido, compressão, expansibilidade e multi-linguagem.
Atualmente alguns requerimentos mencionados não fazem muito sentido, porém estamos falando de uma época onde o principal sistema de arquivos onde esses títulos rodavam era o FAT32.
Sempre gostei da ideia de empacotar os assets
do jogo num único arquivo comprimido. O PhysicsFS permite “montar” diretórios e arquivos comprimidos como se fossem um único diretório, com todos os arquivos estruturados dentro dos seus respectivos diretórios; algo semelhante ao que o UnionFS, OverlayFS e similares fazem.
Outra vantagem é a segurança, pois o processo fica restrito à aquele(s) diretório(s) previamente especificado(s).
Usar o physfs
com a SDL é bem simples, veja como é o processo de montar um arquivo comprimido e carregar uma imagem:
int main(int argc, char *argv[]) {
PHYSFS_init(argv[0]);
SDL_Init(SDL_INIT_VIDEO);
// monta o arquivo `assets.7z` como se fosse um diretório.
PHYSFS_mount("assets.7z", "/", 0);
SDL_Window * window = SDL_CreateWindow(
NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI);
SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
// carrega o arquivo `texture001.tga` que está dentro de `assets.7z`.
SDL_RWops * rwops = PHYSFSRWOPS_openRead("texture001.tga");
// carrega a textura.
SDL_Surface * surface = IMG_Load_RW(rwops, 1);
SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
SDL_bool running = SDL_TRUE;
while(running) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
running = SDL_FALSE;
}
}
// desenha a textura na janela.
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
SDL_Delay(1000 / 60);
}
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Legal né?
O SQLite é provavelmente um dos componentes de software mais utilizados no mundo, está presente em todo o lugar; se estiver lendo esse texto num Android deve ter pelo menos umas 3 cópias dele na suas mãos! SQLite é como um fopen(3)
com esteroides.
Lendo o texto que menciono no inicio do texto, penso “E se eu usar SQLite no lugar do PhysicsFS?”
Embora o SQLite possua uma forma prática de ser fazer o que farei a seguir, o SQLite Archive Files, irei apresentar o passo a passo.
Primeiro vamos criar uma tabela com dois campos, um deles para indentificação e o outro com o conteúdo binario em si.
O SQLite (e a grande maioria dos bancos de dados) não suportam armazenar dados binários, para isso existe um tipo de dados especial chamado BLOB (Binary Large OBject).
sqlite3 assets.db "CREATE TABLE IF NOT EXISTS assets (key TEXT PRIMARY KEY, blob BLOB);"
E é isso. O campo key
é uma chave primaria e portanto tem um índice próprio, como sei?
$ sqlite3 assets.db
SQLite version 3.24.0 2018-06-04 14:10:15
Enter ".help" for usage hints.
sqlite> .schema assets
CREATE TABLE assets (key TEXT PRIMARY KEY, blob BLOB);
sqlite> .indexes assets
sqlite_autoindex_assets_1
O próximo passo é inserir o arquivo da textura texture001.tga
que será usada:
sqlite3 assets.db "INSERT INTO assets(key, blob) VALUES ('texture001', readfile('texture001.tga'));"
O SQLite tem uma função readfile
que carrega o arquivo diretamente.
É possível verificar o tamanho do blob com a função length
:
$ sqlite3 assets.db
SQLite version 3.24.0 2018-06-04 14:10:15
Enter ".help" for usage hints.
sqlite> select key, length(blob) from assets;
texture001|3686418
Que é exatamente o mesmo do arquivo original:
$ stat -f%z texture001.tga
3686418
Adaptando o exemplo acima para a API em C do SQLite temos:
sqlite3 * db;
// abre o arquivo do banco de dados do sqlite.
sqlite3_open("assets.db", &db);
// ...
// preparamos a query.
const char * sql = "SELECT blob FROM assets WHERE key = ?";
sqlite3_stmt * stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
// "atrela" o valor `texture001` no primeiro parâmetro do sql, o `?`.
sqlite3_bind_text(stmt, 1, "texture001", -1, SQLITE_STATIC);
// executa a query "SELECT blob FROM assets WHERE key = 'texture001'".
sqlite3_step(stmt);
// criamos um `SDL_RWops` com os bytes do blob.
int bytes = sqlite3_column_bytes(stmt, 0);
const void * buffer = sqlite3_column_blob(stmt, 0);
SDL_RWops * rwops = SDL_RWFromConstMem(buffer, (sizeof(unsigned char) * bytes));
// finaliza (responsável por liberar a memória retornada por sqlite3_column_blob e outros recursos.)
sqlite3_reset(stmt);
// (como anteriormente) carrega a textura.
SDL_Surface * surface = IMG_Load_RW(rwops, 1);
SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
Usando a função SDL_GetPerformanceFrequency para mensurar o trecho responsável apenas por carregar a textura obtive os seguintes resultados:
$ file texture001.tga
texture001.tga: Targa image data - RGB 1280 x 960 x 24
$ ls -lh texture001.tga | awk '{print $5}'
3.5M
for i in {1..10}; do ./physfs; done
42.060247
40.972251
38.589466
40.684438
43.327696
38.578994
...
for i in {1..10}; do ./sqlite; done
27.433850
30.553595
27.706754
27.282197
27.561867
27.853982
...
IMG_Load("texture001.tga")
(A.K.A. diretamente)for i in {1..10}; do ./a.out; done
22.792655
23.667172
22.286974
23.551452
22.094010
23.657177
...
Acredito que se utilizado compressão no SQLite o uso de disco seria reduzido e como consequência resultados ainda melhores.
Uma das principais características do software é que ele não funciona e precisa constantemente ser remendado, e nos jogos não é diferente.
O MPQ tem um mecanismo de patches, como na época a maioria dos jogos eram distribuídas em mídias somente leitura, como o CD-ROM, era preciso uma outra abordagem, já que não era possível reescrever o .mpq
original, portanto era criado uma espécie de corrente, então após o jogo carregar, as alterações eram aplicadas em cima, na mesma sequencia de que foram publicadas.
A ideia por trás de usar o SQLite no lugar do PhysicsFS é de aproveitar algumas características de um banco de dados, que é de… criar, atualizar, modificar e deletar de forma atômica!
O arquivo de update do jogo poderia ser um conjunto de instruções SQL.
Vamos desconsiderar o binário do jogo por hora, e vamos supor que uma nova textura foi adicionada no banco de dados do desenvolvedor, e por algum motivo desconhecido ele é preguiçoso e usou a ferramenta sqldiff
para gerar o patch e não schema migration.
sqlite3 assets.db "INSERT INTO assets(key, blob) VALUES ('texture002', readfile('texture002.jpg'));"
Estou usando texturas como exemplo pois geralmente é o tipo de arquivo que ocupa mais espaço em disco dos jogos. O exemplo vale para qualquer tipo de arquivo… seja textos, scripts, shaders, etc.
$ sqldiff old.db assets.db > patch01.sql
$ # checando o conteúdo da atualização.
$ head -c 100 up.sql
INSERT INTO assets(rowid,"key", blob) VALUES(2,'texture002',x'ffd8ffe000104a46494600010100000100010%
$ ls -lh patch01.sql | awk '{print $5}'
663K
$ tar -cvzf patch01.tgz patch01.sql
$ ls -lh patch01.tgz | awk '{print $5}'
363K
Como se trata de um arquivo
.jpeg
representado em hexadecimal dentro de um.sql
os ganhos com compressão são pequenos.
Agora basta a nossa ferramenta responsável por atualizar o jogo aplicar os patches na mesma sequencia que foram gerados.
Essa é uma forma bem simples e descomplicada de atualizar o jogo e é algo bem resolvido no mundo dos banco de dados há décadas.
É possível criar updates ainda menores, o Google Chrome tem um projeto chamado courgette que usa a ferramenta bsdiff
combinada com um outro algoritmo descrito no link, podemos usar o bsdiff para gerar o patch do asset a ser atualizado e no cliente usar o bspatch
para aplicar a modificação.
Além de todas essas vantagens, o uso do SQLite ainda possibilita o data-driven programming.
Pretendo utilizar o SQLite como descrito num novo projeto chamado Winterphobos, um motor de jogos minimalista, e uma das premissas é ser totalmente “scriptável” em lua com entity–component–system .
por Rodrigo Delduca (rodrigodelduca@gmail.com) em 27 de December de 2018 às 00:00
Fala pessoal, tudo bom?
Nos vídeos abaixo, vamos aprender como implementar alguns dos algoritmos de ordenação usando Python.
Como o algoritmo funciona: Como implementar o algoritmo usando Python: https://www.youtube.com/watch?v=Doy64STkwlI.
Como implementar o algoritmo usando Python: https://www.youtube.com/watch?v=B0DFF0fE4rk.
Código do algoritmo
def sort(array):
for final in range(len(array), 0, -1):
exchanging = False
for current in range(0, final - 1):
if array[current] > array[current + 1]:
array[current + 1], array[current] = array[current], array[current + 1]
exchanging = True
if not exchanging:
break
Como o algoritmo funciona: Como implementar o algoritmo usando Python: https://www.youtube.com/watch?v=vHxtP9BC-AA.
Como implementar o algoritmo usando Python: https://www.youtube.com/watch?v=0ORfCwwhF_I.
Código do algoritmo
def sort(array):
for index in range(0, len(array)):
min_index = index
for right in range(index + 1, len(array)):
if array[right] < array[min_index]:
min_index = right
array[index], array[min_index] = array[min_index], array[index]
Como o algoritmo funciona: Como implementar o algoritmo usando Python: https://www.youtube.com/watch?v=O_E-Lj5HuRU.
Como implementar o algoritmo usando Python: https://www.youtube.com/watch?v=Sy_Z1pqMgko.
Código do algoritmo
def sort(array):
for p in range(0, len(array)):
current_element = array[p]
while p > 0 and array[p - 1] > current_element:
array[p] = array[p - 1]
p -= 1
array[p] = current_element
Como o algoritmo funciona: Como implementar o algoritmo usando Python: https://www.youtube.com/watch?v=Lnww0ibU0XM.
Como implementar o algoritmo usando Python - Parte I: https://www.youtube.com/watch?v=cXJHETlYyVk.
Código do algoritmo
def sort(array):
sort_half(array, 0, len(array) - 1)
def sort_half(array, start, end):
if start >= end:
return
middle = (start + end) // 2
sort_half(array, start, middle)
sort_half(array, middle + 1, end)
merge(array, start, end)
def merge(array, start, end):
array[start: end + 1] = sorted(array[start: end + 1])
Quando estamos escrevendo um código qualquer, possivelmente
a expressão que mais utilizamos é o if
. Para qualquer
tarefas que buscamos automatizar ou problemas que buscamos
resolver, sempre acabamos caindo em lógicas como "Se isso
acontecer, então faça aquilo, senão faça aquele outro...".
Quando estamos falando de ações a serem executadas, pessoalmente gosto da forma com que o código fica organizado em python quando usamos este tipo de condições, por exemplo:
if vencer_o_thanos:
restaurar_a_paz()
else:
foo()
Graças a indentação e ao espaçamento, vemos onde onde começa e/ou
termina o bloco executado caso a varável vencer_o_thanos
seja
True
. Quanto mais if
's você aninhar, mais bonito seu
código fica e em momento algum o mesmo se torna mais confuso
(ao menos, não deveria se tornar). Entretanto, sempre fico
extremamente incomodado quando tenho de escrever um bloco apenas
marcar uma variável, como por exemplo:
if vencer_o_thanos:
paz = True
else:
paz = False
Por isso, para trabalhar com variáveis que possuem um valor condicional, gosto sempre de trabalhar com expressões condicionais, ou como costumam ser chamadas, operadores ternários.
Operadores ternários são todos os operadores que podem receber três operandos. Como as expressões condicionais costumam ser os operadores ternários mais populares nas linguagens em que aparecem, acabamos por associar estes nomes e considerar que são a mesma coisa. Cuidado ao tirar este tipo de conclusão, mesmo que toda vogal esteja no alfabeto, o alfabeto não é composto apenas por vogais.
A estrutura de uma expressão condicional é algo bem simples, veja só:
paz = True if vencer_o_thanos else False
tipo_de_x = "Par" if x % 2 == 0 else "impar"
Resumidamente, teremos um valor seguido de uma condição e por fim seu valor caso a condição seja falsa. Pessoalmente acredito que apesar de um pouco diferente, essa forma de escrita para casos como o exemplificado acima é muito mais clara, mais explicita.
Se você fizer uma tradução literal das booleanas utilizadas no primeiro exemplo,
lerá algo como paz é verdadeira caso vencer_o_thanos, caso contrário é Falsa.
já o segundo exemplo fica mais claro ainda, pois lemos algo como
tipo_de_x é par caso o resto da divisão de x por 2 seja 0, se não, tipo_de_x é impar.
.
Interpretar código dessa forma pode ser estranho para um programador. Interpretar uma abertura de chave ou uma indentação já é algo mas natural. Todavia, para aqueles que estão começando, o raciocínio ocorre de forma muito mais parecida com a descrita acima. Espero que tenham gostado do texto e que esse conhecimento lhes seja útil.
por Vitor Hugo de Oliveira Vargas em 06 de October de 2018 às 12:21
Hacktoberfest is an amazing campaign by Digital Ocean and Github, you contribute with at least 5 open source Pull Requests and then you get a T-shirt and some stickers.
Maintainers are encouraged to label and organize the issues to be worked on.
Register at: https://hacktoberfest.digitalocean.com/
I will list here some of my Projects and the issues I am expecting to get some contributions.
Dynaconf is a library for settings management in any kind of Python project.
Skills needed: CI, Python, decorators, Python Module Initialization, Data Structures and Python data Model
Issues: https://github.com/rochacbruno/dynaconf/issues
Highlights:
Flasgger is the project powering the http://httpbin.org website, it allows you to document your Flask API and serves Swagger UI.
Skills needed: API REST, Flask, OPenAPISpec, Decorators, Python data model.
Issues: https://github.com/rochacbruno/flasgger/issues
HIghlights:
Easy way to add maps to Flask views.
Skills needed: Flask, Google APIs
Issues: https://github.com/rochacbruno/Flask-GoogleMaps
Easy way to protect your Flask views with login.
Skills needed: Python, Flask, environment variables, templating, CSS, HTML, Bootstrap.
Issues: https://github.com/rochacbruno/flask_simplelogin/issues
Highlights:
Quokka is a Content Management Framework, which is in process of rewriting, the idea is having a core written in Flask, use Pelican themes and allow generation of static websites.
Skills needed: Flask, Python, Templating, MongoDB
Issues: https://github.com/rochacbruno/quokka
All the issues are good, as it is a re-writing from scratch project.
This is a Guide for Pythonistas who are learning Rust language
Issues: https://github.com/rochacbruno/py2rs/issues
The project really needs more examples to be written and also more comparison and fixes. Any kind of contributions are welcome.
This is a call 4 papers system (used as didatic example only)
Skills needed: Flask, Templating, mongoDB
Today I got a pleasant surprise: Olga Botvinnik posted on Twitter about a poster she is presenting at the Beyond the Cell Atlas conference and she name-dropped a bunch of people that helped her. The cool thing? They are all open source developers, and Olga interacted thru GitHub to ask for features, report bugs and even submit pull requests.
That's what open science is about: collaboration, good practices, and in the end coming up with something that is larger than each individual piece. Now sourmash is better, bamnostic is better, reflow is better. I would like to see this becoming more and more common =]
Procure seu colaborador favorito do KDE na Foto em grupo oficial do Akademy 2018
Estive em Viena para participar do Akademy 2018, o encontro anual do KDE. Este foi o meu quarto Akademy, sendo antecedido por Berlin’2012 (na verdade, Desktop Summit ), Brno’2014, e Berlin’2016 (junto com a QtCon). Interessante, vou ao Akademy a cada 2 anos – pretendo melhorar isso já no próximo.
Após uma viagem muito longa, pude finalmente encontrar “cabeças de engrenagem” de todas as partes do mundo, incluindo o próprio Brasil. Vi velhos e novos amigos trabalhando juntos para melhorar a experiência de utilizar um computador com software livre, cada qual contribuindo com pequenas (e alguns, realmente gigantes) partes para tornar isso realidade. Sempre que encontro esse pessoal me sinto reenergizado para seguir com esse trabalho.
Das palestras, gostei muito da feita por Volker sobre o KDE Itinerary, uma nova aplicação para gerenciar passbooks relacionados com viagens. Penso que um software para gerenciar todos os tipos de arquivos passbook (como entradas para shows, cinemas, e mais) como este seria uma interessante adição para a família de softwares do KDE, e um passo anterior à ideia perseguida pelo KDE Itinerary. De qualquer forma, estou na expectativa por novidades deste software.
A palestra sobre criação de transições utilizando o Kdenlive me fez pensar em quão interessante seria um plugin para executar scripts bash (ou python, talvez) de forma a automatizar vários passos realizados por editores nesse software. Inclusive, talves utilizando a KDE Store para compartilhar esses scripts… enfim, muitas ideias.
Conheci Camilo durante o evento. A palestra dele sobre o vvave me deu esperanças por uma nova e interessante aplicação de player multimídia, como uma vez tivemos no passado (saudades Amarok).
A última palestra que chamou minha atenção foi de Nate sobre algumas ideias para melhorar nosso ecossistema. Nate está fazendo um trabalho fabuloso sobre usabilidade, “polindo” nossos software de diversas formas. Recomendo o blog dele para quem quiser ficar acompanhar também. Apesar de eu concordar em geral com boa parte das ideias – por exemplo, é urgente a necessidade de melhorarmos nossa suíte de aplicações pessoais -, eu tive alguns desacordos em outras, em especial a ideia de que os desenvolvedores do KDE se dediquem a contribuir também para o LibreOffice. LibreOffice tem um código fonte completamente diferente, que utiliza tecnologias e práticas nada relacionadas com o que vemos no KDE, e há várias (e para muitos de nós, desconhecidas) influências das diferentes organizações que gerem a The Document Foundation, entidade responsável por desenvolver o LibreOffice. E por fim, nós ainda temos a suíte de escritório Calligra – a idea me soou como “vamos acabar com o Calligra”. De qualquer forma, isso foi apenas um desacordo com uma das sugestões, nada demais.
Após as seções de palestras o Akademy teve batantes sessões de BoF, que são mini-reuniões direcionadas sobre tópicos específicos. Pude participar de algumas, como a sobre o KDE e Qt (é sempre bom manter os olhos sobre esse tópico), KDE Phabricator (Phabricator é um conjunto muito bom de ferramentas para gerenciamento de projetos/repositórios/e afins, mas sofre por conta dos competidores (Gitlab) serem muito mais conhecidos e também por não ter das melhores usabilidades), e MyCroft (gosto da ideia de integração entre o MyCroft e o Plasma, especialmente para casos de uso especĩficos como auxiliar pessoas com deficiência – estou pensando nisso já há alguns meses).
Este ano, Aracele, Sandro e eu realizamos um BoF chamado “KDE in Americas”. A ideia foi apresentar algumas das nossas conquistas para o KDE na América Latina e discutir com o pessoal das demais américas sobre um evento “continental”, trazendo de volta o antigo CampKDE em uma nova edição junto com o LaKademy (o nome secreto é LaKamp :D). Esta ideia ainda precisa de alguma maturação para seguir em frente, mas estamos trabalhando.
Este ano eu tentei realizar o BoF sobre KDE na ciência e Cantor, mas infelizmente eu não tive o feedback necessário sobre potenciais participantes. Vamos ver se no futuro poderemos ter alguns deles acontecendo.
Akademy é um evento fantástico onde você encontra colaboradores do KDE de diferentes frentes e culturas, pode discutir com eles, pegar opiniões sobre projetos atuais e mesmo iniciar novos. Gostaria de agradecer ao KDE e.V. pelo patrocínio que me permitiu ir ao evento e espero ver todos vocês e mais alguns no próximo ano (vou tentar inserir um distúrbio naquela distribuição da minha sequência bianual de participações) no Akademy ou, em algumas semanas, no LaKademy!
Brasileiros no Akademy 2018: KDHelio, Caio, Sandro, Filipe (eu), Tomaz (abaixo), Eliakin, Aracele, e Lays
A quick announcement: I wrote a Rust implementation of ntHash and published
it in crates.io. It implements an Iterator
to take advantage of the
rolling properties of ntHash
which make it so useful in bioinformatics (where
we work a lot with sliding windows over sequences).
It's a pretty small crate, and probably was a better project to learn Rust than doing a sourmash implementation because it doesn't involve gnarly FFI issues. I also put some docs, benchmarks using criterion, and even an oracle property-based test with quickcheck.
More info in the docs, and if you want an optimization versioning bug
discussion be sure to check the ntHash bug?
repo,
which has a (slow) Python implementation and a pretty nice analysis notebook.
sourmash calculates MinHash signatures for genomic datasets, meaning we are reducing the data (via subsampling) to a small representative subset (a signature) capable of answering one question: how similar is this dataset to another one? The key here is that a dataset with 10-100 GB will be reduced to something in the megabytes range, and two approaches for doing that are:
What if we could keep the frontend code from the web service (very user-friendly) but do all the calculations client-side (and avoid the network bottleneck)? The main hurdle here is that our software is implemented in Python (and C++), which are not supported in browsers. My first solution was to write the core features of sourmash in JavaScript, but that quickly started hitting annoying things like JavaScript not supporting 64-bit integers. There is also the issue of having another codebase to maintain and keep in sync with the original sourmash, which would be a relevant burden for us. I gave a lab meeting about this approach, using a drag-and-drop UI as proof of concept. It did work but it was finicky (dealing with the 64-bit integer hashes is not fun). The good thing is that at least I had a working UI for further testing1
In "Oxidizing sourmash: Python and FFI" I described my road to learn Rust,
but something that I omitted was that around the same time the WebAssembly
support in Rust started to look better and better and was a huge influence in
my decision to learn Rust. Reimplementing the sourmash C++ extension in Rust and
use the same codebase in the browser sounded very attractive,
and now that it was working I started looking into how to use the WebAssembly
target in Rust.
From the official site,
WebAssembly (abbreviated Wasm) is a binary
instruction format for a stack-based
virtual machine. Wasm is designed as a
portable target for compilation of high-level
languages like C/C++/Rust, enabling deployment
on the web for client and server applications.
You can write WebAssembly by hand, but the goal is to have it as lower level target for other languages. For me the obvious benefit is being able to use something that is not JavaScript in the browser, even though the goal is not to replace JS completely but complement it in a big pain point: performance. This also frees JavaScript from being the target language for other toolchains, allowing it to grow into other important areas (like language ergonomics).
Rust is not the only language targeting WebAssembly: Go 1.11 includes experimental support for WebAssembly, and there are even projects bringing the scientific Python to the web using WebAssembly.
With the Rust implementation in place and with all tests working on sourmash, I
added the finishing touches using wasm-bindgen
and built an NPM package using
wasm-pack
: sourmash is a Rust codebase compiled to WebAssembly and ready
to use in JavaScript projects.
(Many thanks to Madicken Munk, who also presented during SciPy about how they used Rust and WebAssembly to do interactive visualization in Jupyter and helped with a good example on how to do this properly =] )
Since I already had the working UI from the previous PoC, I refactored the code to use the new WebAssembly module and voilà! It works!2. 3 But that was the demo from a year ago with updated code and I got a bit better with frontend development since then, so here is the new demo:
Drag & drop a FASTA or FASTQ file here to calculate the sourmash signature.
For the source code for this demo, check the sourmash-wasm directory.
The proof of concept works, but it is pretty useless right now. I'm thinking about building it as a Web Component and making it really easy to add to any webpage4.
Another interesting feature would be supporting more input formats (the GMOD project implemented a lot of those!), but more features are probably better after something simple but functional is released =P
Where we will go next? Maybe explore some decentralized web technologies like IPFS and dat, hmm? =]
even if horrible, I need to get some design classes =P ↩
the first version of this demo only worked in Chrome because they implemented the BigInt proposal, which is not in the official language yet. The funny thing is that BigInt would have made the JS implementation of sourmash viable, and I probably wouldn't have written the Rust implementation =P. Turns out that I didn't need the BigInt support if I didn't expose any 64-bit integers to JS, and that is what I'm doing now. ↩
Along the way I ended up writing a new FASTQ parser... because it wouldn't be bioinformatics if it didn't otherwise, right? =P ↩
or maybe a React component? I really would like to have something that works independent of framework, but not sure what is the best option in this case... ↩