2014-10-18 4 views
0

Как я могу использовать стандартную библиотеку Python для получения файлового объекта, молчаливо обеспечивая его актуальность из какого-либо другого места?Доступ к локальному файлу, но убедитесь, что он обновлен

Программа, над которой я работаю, нуждается в доступе к набору файлов локально; это только обычные файлы.

Но эти файлы являются локальными кешированными копиями документов, доступных на удаленных URL-адресах - каждый файл имеет канонический URL-адрес для содержимого этого файла.

(я пишу здесь о HTTP URL-адресе, но я ищу решение, которое не относится к какому-либо конкретному протоколу удаленного кокетливому.)

Я хотел бы получить API для «get_file_from_cache», что выглядит как:

file_urls = { 
     "/path/to/foo.txt": "http://example.org/spam/", 
     "other/path/bar.data": "https://example.net/beans/flonk.xml", 
     } 

for (filename, url) in file_urls.items(): 
    infile = get_file_from_cache(filename, canonical=url) 
    do_stuff_with(infile.read()) 
  • Если изменение метка время локального файла не существенно раньше, чем Last-Modified метки времени для документа на соответствующем URL, get_file_from_cache просто возвращает объект файла без изменения файла.

  • Локальный файл может устареть (его модификация метка может быть значительно старше, чем Last-Modified метки времени от соответствующего URL). В этом случае get_file_from_cache должен сначала прочитать содержимое документа в файл, а затем вернуть файл .

  • Возможно, локальный файл еще не существует. В этом случае get_file_from_cache должен сначала прочитать содержимое документа с соответствующего URL-адреса, создать локальный файл, а затем вернуть объект файла.

  • Удаленный URL-адрес может быть недоступен по какой-либо причине. В этом случае get_file_from_cache должен просто вернуть файл-объект, или если не может быть выполнен, поднимите ошибку.

Так что это похоже на кеш объектов HTTP. За исключением тех случаев, когда эти , как правило, связаны URL-адресами с локальными файлами, скрытая реализация , мне нужен API , который фокусируется на локальных файлах, а удаленный запрашивает скрытую деталь реализации.

Что-то вроде этого существует в библиотеке Python или как простой код с его помощью? С или без специфики HTTP и URL-адресов существует ли какой-то рецепт общего кэширования уже , реализованный со стандартной библиотекой?

Этот локальный кэш файл (игнорируя spcifics из URL-адресов и доступа к сети) кажется, точно такую ​​вещь, которая легко ошибиться в множество способов, и поэтому они должны иметь одну очевидную реализацию доступной.

Мне повезло? Что вы посоветуете?

+0

Посмотрите на [ETag] (https://en.wikipedia.org/wiki/HTTP_ETag) , – User

+0

@User: Это может сработать, если сайты, которые bignose хотят получить доступ, делают правильные вещи со своими etags. OTOH, он не против, если кешированная версия немного устарела, и etags не могут с этим поделать. –

ответ

0

Из быстрого Googling я не мог найти существующую библиотеку, которая может сделать, хотя я был бы удивлен, если бы не было такого. :)

В любом случае, это один из способов сделать это, используя популярный модуль Requests. Было бы довольно легко адаптировать этот код для использования urllib/urlib2.

#! /usr/bin/env python 

''' Download a file if it doesn't yet exist in offline cache, or if the online 
    version is more than age seconds newer than the cached version. 

    Example code for 
http://stackoverflow.com/questions/26436641/access-a-local-file-but-ensure-it-is-up-to-date 

    Written by PM 2Ring 2014.10.18 
''' 

import sys 
import os 
import email.utils 
import requests 


cache_path = 'offline_cache' 

#Translate local file names in cache_path to URLs 
file_urls = { 
    'example1.html': 'http://www.example.com/', 
    'badfile': 'http://httpbin.org/status/404', 
    'example2.html': 'http://www.example.org/index.html', 
} 


def get_headers(url): 
    resp = requests.head(url) 
    print "Status: %d" % resp.status_code 
    resp.raise_for_status() 
    for k,v in resp.headers.items(): 
     print '%-16s : %s' % (k, v) 


def get_url_mtime(url): 
    ''' Get last modified time of an online file from the headers 
    and convert to a timestamp 
    ''' 
    resp = requests.head(url) 
    resp.raise_for_status() 
    t = email.utils.parsedate_tz(resp.headers['last-modified']) 
    return email.utils.mktime_tz(t) 


def download(url, fname): 
    ''' Download url to fname, setting mtime of file to match url ''' 
    print >>sys.stderr, "Downloading '%s' to '%s'" % (url, fname) 
    resp = requests.get(url) 
    #print "Status: %d" % resp.status_code 
    resp.raise_for_status() 

    t = email.utils.parsedate_tz(resp.headers['last-modified']) 
    timestamp = email.utils.mktime_tz(t) 
    #print 'last-modified', timestamp 

    with open(fname, 'wb') as f: 
     f.write(resp.content) 
    os.utime(fname, (timestamp, timestamp)) 


def open_cached(basename, mode='r', age=0): 
    ''' Open a cached file. 

    Download it if it doesn't yet exist in cache, or if the online 
    version is more than age seconds newer than the cached version.''' 

    fname = os.path.join(cache_path, basename) 
    url = file_urls[basename] 
    #print fname, url 

    if os.path.exists(fname): 
     #Check if online version is sufficiently newer than offline version 
     file_mtime = os.path.getmtime(fname) 
     url_mtime = get_url_mtime(url) 
     if url_mtime > age + file_mtime: 
      download(url, fname) 
    else: 
     download(url, fname) 

    return open(fname, mode) 


def main(): 
    for fname in ('example1.html', 'badfile', 'example2.html'): 
     print fname 
     try: 
      with open_cached(fname, 'r') as f: 
       for i, line in enumerate(f, 1): 
        print "%3d: %s" % (i, line.rstrip()) 
     except requests.exceptions.HTTPError, e: 
      print >>sys.stderr, "%s '%s' = '%s'" % (e, file_urls[fname], fname) 
     print 


if __name__ == "__main__": 
    main() 

Конечно, для использования в реальных условиях вы должны добавить правильную проверку ошибок.

Вы можете заметить, что я определил функцию get_headers(url), которая никогда не вызывается; Я использовал его во время разработки & понял, что он может пригодиться при расширении этой программы, поэтому я ее оставил. :)

Смежные вопросы