CuriousY A world with wonder

使用setuptools实现pip install时执行指定脚本

| Comment

简单说,就是重新实现一个安装类,然后让setuptools在安装时调用我们的这个安装类。为了保留原生的安装类中的方法,我们直接继承setuptools中的安装类,然后重写其中的run方法即可:

from setuptools.command.install import install

class CustomInstallCommand(install):
    """Customized setuptools install command"""

    def run(self):
        install.run(self)
        YOUR_FUNCTION()  # Replace with your code

然后将setup函数中的cmdclass的“install”置为上面重写的安装类:

setuptools.setup(
    ...
    ,
    cmdclass={
        'install': CustomInstallCommand,
    },
)

此外,需要注意的是,如果希望将package上传至PyPI,别人pip install时也能执行你的脚本,则必须要使用源码打包的方式,即:

python setup.py sdist

不能是:

python setup.py sdist bdist_wheel

因为后者在安装时并不会再去执行setup.py(这种方式可以理解为在上传package时将已经执行过setup.py的结果打包上传了)。

Reference

  1. Running custom code with pip install fails
  2. Setuptools – run custom code in setup.py

Upload Python package to PyPI

| Comment

流程###

  1. PyPI上注册一个账号;

  2. 编辑~/.pypirc文件,添加你的账号信息:

    [distutils]
    index-servers =
    	pypi
    
    [pypi]
    repository=https://upload.pypi.org/legacy/
    username={YOUR_USERNAME}
    password={YOUR_PASSWORD}
    
  3. 打包你的项目(前提是你已经用setuptools写好了一个setup.py):

    sudo python setup.py sdist bdist_wheel
    
  4. 上传项目:

    twine upload dist/* -r pypi
    

另外,如果你只是想测试下上传的流程,你也可以选择上传到TestPyPI这个repo上。

坑###

HTTPError: 403 Client Error: You are not allowed to upload to 'xxxx'. for url: https://upload.pypi.org/legacy/

使用twine进行upload出现以上的错误,那么很可能是因为你的项目和已有的项目重名了,可以到https://pypi.python.org/simple/上搜一下看看是否重名。解决的方法自然就是修改一下setup.py中setup函数中的name参数,删除之前生成的dist文件夹并重新生成,然后再upload。

使用Cookie实现页面自动登录

| Comment

关于Cookie

我们知道一个页面关闭之后,关于这个页面的前端代码在内存里的东西就都没有了。如果想要进行前端数据的持久化,目前主要有Cookie、LocalStorage和SessionStorage这几种方式。这些方式本质上都是将数据通过浏览器存储在了磁盘上(所以随着浏览器的发展,以后可能还会出现更多的数据持久化的方式呢)。那为什么普遍都选用Cookie来实现页面自动登录的功能呢?

简单地说,因为Cookie有这样一个机制:它会自动出现在前端与后端发送的每一个请求中(以header的形式),即使你没有设置任何Cookie,请求中也会包含一个空的Cookie header。因此只要将用户的认证信息放在Cookie当中,就表示之后的每个请求自带了身份验证的功能,服务器端可以通过检查每一条请求的Cookie信息来确认该请求是否“合法”。这里要注意的是,Cookie是和域名绑定的,不同域名的Cookie是相互隔离的,即设置了http://www.google.com的Cookie后,只有该页面下的请求会自动夹带这个设定的Cookie。

另外,Cookie在设置时一般会添加一个过期的时间,即当一个Cookie过期了,浏览器会自动删除该Cookie的内容,然后后端发现过来的请求的Cookie为空,就redirect到登陆页面了。用户重新登陆后,后端会同时更新Cookie的信息。设置过期时间的目的主要还是为了安全,因为Cookie里面一般存储的是根据用户的用户名和密码生成的一段秘钥,用户每次登陆就会去重新生成一段秘钥,这就好比你经常更换密码一样,坏人想破解你的秘钥,可能破解到一半秘钥就变了,你说坏人气不气。(Cookie过期的时候服务器端一般不需要做任何操作?这里如果服务器不去管理Cookie过期的话可能会有安全的隐患,即一个用户很长时间不登陆,虽然Cookie过期了,但后端服务器还是认之前这个过期的Cookie信息的,也就给人足够的时间来破解Cookie的信息并伪装成该用户登陆了)

以上,也就解释了为什么有些网站很久才需要重新登陆一次,而有些网站没过多久就又要输用户名密码了。

Read more

Client side routing VS. server side routing

| Comment

问题始于一个项目中将前端和后端整合时发现的一个bug:这个项目所有页面的路由都是由前端代码完成的(react-router),而后端代码则是用Flask写的,且后端只在根路径(http://localhost:5000/)渲染了前端的代码,此为前提。当用户从根路径打开页面时,一切正常。但当用户跳过根路径,而直接在浏览器中输入一个子路径时(比如http://localhost:5000/about)出了问题,返回了404。并且用户在任意一个子路径进行刷新页面的操作时,也会返回404。

Client side routing VS. server side routing

The first big thing to understand about this is that there are now 2 places where the URL is interpreted, whereas there used to be only 1 in ‘the old days’. In the past, when life was simple, some user sent a request for http://example.com/about to the server, which inspected the path part of the URL, determined the user was requesting the about page and then sent back that page.

With client-side routing, which is what React-Router provides, things are less simple. At first, the client does not have any JS code loaded yet. So the very first request will always be to the server. That will then return a page that contains the needed script tags to load React and React Router etc. Only when those scripts have loaded does phase 2 start. In phase 2, when the user clicks on the ‘About us’ navigation link for example, the URL is changed locally only to http://example.com/about (made possible by the History API), but no request to the server is made. Instead, React Router does it’s thing on the client side, determines which React view to render and renders it. Assuming your about page does not need to make any REST calls, it’s done already. You have transitioned from Home to About Us without any server request having fired.

简单地说,client side routing就是通过前端代码的一顿操作,将本该由server端处理的工作给拦截了,并且自己给做了。为什么会有这种需求呢,其实熟悉一些前端就很好理解了:交给server端处理,大部分情况就是重新渲染另一个页面,一般都伴随着页面的刷新,而对于前端代码而言,刷新页面就如同运行后端代码时重启系统,所有内存中存储的东西都没了,前端上很多复杂的操作也就很难实现了。为了能将“命运”掌握在自己手里,client side routing的技术也就应运而生。

Read more

Elasticsearch query system

| Comment

Data schema

刚上手Elasticsearch,就觉得它和GraphQL有一些相似,不仅仅在于他们都是用于查询的系统,而且它们对于数据都需要提前定义好schema1(在Elasticsearch里面也叫mapping)。当然,它们本质的不同在于前者是用于做数据查询的,后者用于构建API,而API对应的处理不一定是数据的查询(或者可以理解为Elasticsearch有点像是GraphQL+数据查询系统)。

在Elasticsearch里面,定义数据的schema并不是必须的,如果没有定义好schema,那么Elasticsearch会根据内容来猜测相应的类型2,但定义好了为之后的查询也提供了便利,还能减少一些歧义。Elasticsearch内置了许多的数据类型3,从常见的text类型到复杂的object类型等等,足够应付大部分的数据了。尤其需要注意的是不同的数据类型,它们所支持的搜索方式可能也不相同(因为检索的方法就不同),比如keyword类型的field只有查询完全匹配时才能找到(它不支持评分查询,即使你使用了matchmatch_phrase_prefix对其进行搜索最终也会退化为term的搜索方式)。

顺带提一下,Elasticsearch是可以通过REST接口直接上传包含数据的文件的,就像文档中那样1,但需要注意的是文档中提供的数据其实是已经处理过的数据,比如它包含了下面这些内容并不是原数据的内容。所以想要拿自己的数据在Elasticsearch上面玩一玩的话,还是通过Logstash来导入数据吧。

{
  "index": {
    "_index": "shakespeare",
    "_type": "act",
    "_id": 0
  }
}

Lucene syntax VS. Query DSL

我们知道Elasticsearch是基于Lucene构建起来的,所以它自然支持Lucene的搜索语法。但同时它又有自己的一套DSL,它们的区别在于,前者要更高级一些(似乎在Kibana的Discover页面就是通过前者来进行查询的),通过解析才能得到后者4(解析的结果可以通过Search Profiler来查看,参考FAQ第5条)。

下面这两种方式得到的结果是一样的,前者里面包含的正是Lucene的语法,后者是对应的DSL:

{
  "query": {
    "query_string": {
      "query": "play_name: \"Henry IV\""
    }
  }
}
{
  "query": {
    "term": {
      "play_name": "Henry IV"
    }
  }
}
Read more
| Page 10 of 25 |