Bagaimana cara menghapus semua datastore di Google App Engine?


122

Adakah yang tahu cara menghapus semua datastore di Google App Engine ?


2
db.delete (db.Query (keys_only = True)). Detail lebih lanjut di sini stackoverflow.com/a/10856555/290340 .
Evan Plaice

4
Seperti yang ditunjukkan oleh @systempuntoout di bawah, GAE sekarang memiliki Admin Datastore yang memungkinkan Anda menghapus entitas secara massal tanpa pengkodean apa pun, antara lain. Fitur itu perlu dimunculkan di sini daripada dikubur di komentar ke-3.
ralfoide

Admin Datastore tidak berfungsi (halaman memuat iframe ke host yang tidak ada), jadi kami masih perlu menggunakan metode db.delete.

Untuk menghapus semua data di server pengembangan, /path/to/google_appengine/dev_appserver.py --clear_datastore yes myappname/ jalankan perintah berikut pada prompt cmd: di mana myappname adalah direktori Anda yang berisi file app.yaml Anda untuk aplikasi .. Anda perlu cd ke jalur direktori ini .. kredit: Steven Almeroth dan Melllvar untuk jawaban di bawah
gsinha

Jawaban:


69

Jika Anda berbicara tentang live datastore , buka dasbor untuk aplikasi Anda (login di appengine) lalu datastore -> dataviewer, pilih semua baris untuk tabel yang ingin Anda hapus dan tekan tombol hapus (Anda harus lakukan ini untuk semua tabel Anda). Anda dapat melakukan hal yang sama secara terprogram melalui remote_api (tapi saya tidak pernah menggunakannya).

Jika Anda berbicara tentang pengembangan datastore , Anda hanya perlu menghapus file berikut: "./WEB-INF/appengine-generated/local_db.bin" . File tersebut akan dibuat untuk Anda lagi saat Anda menjalankan server pengembangan dan Anda akan mendapatkan db yang jelas.

Pastikan untuk membersihkan proyek Anda setelahnya.

Ini adalah salah satu trik kecil yang berguna saat Anda mulai bermain dengan Mesin Aplikasi Google. Anda akan mendapati diri Anda menahan objek ke dalam datastore kemudian mengubah model objek JDO untuk entitas persistable Anda yang berakhir dengan data usang yang akan membuat aplikasi Anda mogok di semua tempat.


16
Ada parameter -c ke dev_appserver.py untuk dihapus dari pengembangan datastore.
svrist

1
@svrist Tapi itu hanya berlaku untuk mesin aplikasi Python. Adakah yang tahu bagaimana cara pintas untuk melakukannya di Jawa? (Sementara itu, saran JohnIdol bekerja dengan baik.)
mgiuca

2
Terima kasih @ John: Dimana jalur yang tepat di MAC OSX?
George Nguyen

3
Di mana jalur di Windows?
Shane Best

2
@ShaneBest jalur di windows adalah sesuatu seperti ./target/yourappid-1.0-SNAPSHOT/WEB-INF/appengine-generated/local_db.bin
morpheus

58

Pendekatan terbaik adalah metode API jarak jauh seperti yang disarankan oleh Nick, dia adalah insinyur App Engine dari Google , jadi percayalah padanya.

Ini tidak terlalu sulit untuk dilakukan, dan 1.2.5 SDK terbaru menyediakan remote_shell_api.py dari rak. Jadi, unduh SDK baru. Kemudian ikuti langkah-langkahnya:

  • hubungkan server jarak jauh di baris perintah Anda: remote_shell_api.py yourapp /remote_api Shell akan meminta info login Anda, dan jika diotorisasi, akan membuat shell Python untuk Anda. Anda perlu menyiapkan penangan url untuk / remote_api di app.yaml Anda

  • ambil entitas yang ingin Anda hapus, kodenya terlihat seperti ini:

    from models import Entry
    query = Entry.all(keys_only=True)
    entries =query.fetch(1000)
    db.delete(entries)
    \# This could bulk delete 1000 entities a time

Perbarui 2013-10-28 :

  • remote_shell_api.pytelah diganti oleh remote_api_shell.py, dan Anda harus terhubung dengan remote_api_shell.py -s your_app_id.appspot.com, menurut dokumentasi .

  • Ada fitur eksperimental baru Admin Datastore , setelah mengaktifkannya dalam pengaturan aplikasi, Anda dapat menghapus sekaligus mencadangkan datastore Anda melalui antarmuka web.


17
Sebenarnya, Anda tidak perlu mengambilnya. Hanya db.delete (Entry.all ()) akan melakukannya.
unduh

4
Anda perlu melakukan ini dalam 500 kumpulan entitas atau Anda akan mendapatkan: BadRequestError: tidak dapat menghapus lebih dari 500 entitas dalam satu panggilan
marcc

1
Sekadar informasi, agar Anda dapat menggunakan api jarak jauh, Anda harus mengaktifkannya terlebih dahulu di aplikasi Anda menggunakan bawaan: - remote_api: di file YAML Anda. info lebih lanjut ada di developers.google.com/appengine/articles/remote_api
Zaffiro

2
Setidaknya tambahkan 'keys_only = True' saat Anda memanggil Entry.all (). Tidak perlu mengambil seluruh entri jika Anda tidak perlu memeriksa datanya. Jika tidak, Anda hanya membuang-buang siklus komputasi.
Evan Plaice

1
+1 ... tapi: Pada 2013, remote_shell_api.py tidak ada. Nama skrip saat ini adalah remote_api_shell.py. Selain itu, jika Anda menggunakan ndb (yang dilakukan kebanyakan orang saat ini), cara yang disarankan untuk menggunakan ndb.delete_multi (model.Entry.query (). Fetch (keys_only = True))
Uri

27

Cara tercepat dan efisien untuk menangani penghapusan massal di Datastore adalah dengan menggunakan API mapper baru yang diumumkan di Google I / O terbaru .

Jika bahasa pilihan Anda adalah Python , Anda hanya perlu mendaftarkan mapper Anda di file mapreduce.yaml dan mendefinisikan fungsi seperti ini:

from mapreduce import operation as op
def process(entity):
 yield op.db.Delete(entity)

Di Java, Anda harus melihat artikel ini yang menyarankan fungsi seperti ini:

@Override
public void map(Key key, Entity value, Context context) {
    log.info("Adding key to deletion pool: " + key);
    DatastoreMutationPool mutationPool = this.getAppEngineContext(context)
            .getMutationPool();
    mutationPool.delete(value.getKey());
}

EDIT:
Sejak SDK 1.3.8, ada fitur admin Datastore untuk tujuan ini


27

Anda dapat menghapus penyimpanan data server pengembangan saat Anda menjalankan server:

/path/to/dev_appserver.py --clear_datastore=yes myapp

Anda juga bisa menyingkat --clear_datastoredengan -c.


5
Tidak yakin apakah itu hal yang baru, tetapi sintaks yang sebenarnya sekarang /path/to/google_appengine/dev_appserver.py --clear_datastore yes myappname/(perhatikan 'ya')
Melllvar

Ini adalah cara paling berguna untuk berulang kali menghapus datastore selama pengembangan. Dengan opsi yang menjadi cepat usang, ada baiknya untuk menyorot bahwa bendera ini masih berlaku pada Juli 2018, dan berfungsi untuk dev_appserver yang dipasang melalui gcloud CLI
Michael

Dalam versi 270.0.0 Google Cloud SDK "--clear_datastore = yes" masih berfungsi dengan tanda sama dengan
franksands

15

Jika Anda memiliki sejumlah besar data, Anda perlu menggunakan skrip untuk menghapusnya. Anda dapat menggunakan remote_api untuk menghapus datastore dari sisi klien dengan cara yang langsung.


11

Ini dia: Buka Admin Datastore, lalu pilih jenis Entitas yang ingin Anda hapus dan klik Hapus. Mapreduce akan menangani penghapusan!


9

Ada beberapa cara yang dapat Anda gunakan untuk menghapus entri dari App Engine Datastore:

masukkan deskripsi gambar di sini

  1. Pertama, pikirkan apakah Anda benar-benar perlu menghapus entri. Ini mahal dan mungkin lebih murah untuk tidak menghapusnya.

  2. Anda dapat menghapus semua entri dengan tangan menggunakan Admin Datastore.

  3. Anda dapat menggunakan API Jarak Jauh dan menghapus entri secara interaktif.

  4. Anda dapat menghapus entri secara terprogram menggunakan beberapa baris kode.

  5. Anda dapat menghapusnya secara massal menggunakan Antrean dan Kursor Tugas.

  6. Atau Anda dapat menggunakan Mapreduce untuk mendapatkan sesuatu yang lebih kuat dan lebih menarik.

Masing-masing metode ini dijelaskan dalam entri blog berikut: http://www.shiftedup.com/2015/03/28/how-to-bulk-delete-entries-in-app-engine-datastore

Semoga membantu!


6

Cara zero-setup untuk melakukan ini adalah dengan mengirim permintaan HTTP kode-arbitrer ke layanan admin yang secara otomatis sudah dimiliki oleh aplikasi Anda yang sedang berjalan:

import urllib
import urllib2

urllib2.urlopen('http://localhost:8080/_ah/admin/interactive/execute',
    data = urllib.urlencode({'code' : 'from google.appengine.ext import db\n' +
                                      'db.delete(db.Query())'}))

Ini hanya berfungsi untuk server pengembangan. Apakah ada produksi yang setara?
Gady

3

Sumber

Saya mendapatkan ini dari http://code.google.com/appengine/articles/remote_api.html .

Buat Konsol Interaktif

Pertama, Anda perlu menentukan konsol appenginge interaktif. Jadi, buat file bernama appengine_console.py dan masukkan ini:

#!/usr/bin/python
import code
import getpass
import sys

# These are for my OSX installation. Change it to match your google_appengine paths. sys.path.append("/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine")
sys.path.append("/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/yaml/lib")

from google.appengine.ext.remote_api import remote_api_stub
from google.appengine.ext import db

def auth_func():
  return raw_input('Username:'), getpass.getpass('Password:')

if len(sys.argv) < 2:
  print "Usage: %s app_id [host]" % (sys.argv[0],)
app_id = sys.argv[1]
if len(sys.argv) > 2:
  host = sys.argv[2]
else:
  host = '%s.appspot.com' % app_id

remote_api_stub.ConfigureRemoteDatastore(app_id, '/remote_api', auth_func, host)

code.interact('App Engine interactive console for %s' % (app_id,), None, locals())



Buat kelas dasar Mapper

Setelah berada di tempatnya, buat kelas Mapper ini. Saya baru saja membuat file baru bernama utils.py dan melemparkan ini:

class Mapper(object):
  # Subclasses should replace this with a model class (eg, model.Person).
  KIND = None

  # Subclasses can replace this with a list of (property, value) tuples to filter by.
  FILTERS = []

  def map(self, entity):
    """Updates a single entity.

    Implementers should return a tuple containing two iterables (to_update, to_delete).
    """
    return ([], [])

  def get_query(self):
    """Returns a query over the specified kind, with any appropriate filters applied."""
    q = self.KIND.all()
    for prop, value in self.FILTERS:
      q.filter("%s =" % prop, value)
    q.order("__key__")
    return q

  def run(self, batch_size=100):
    """Executes the map procedure over all matching entities."""
    q = self.get_query()
    entities = q.fetch(batch_size)
    while entities:
      to_put = []
      to_delete = []
      for entity in entities:
        map_updates, map_deletes = self.map(entity)
        to_put.extend(map_updates)
        to_delete.extend(map_deletes)
      if to_put:
        db.put(to_put)
      if to_delete:
        db.delete(to_delete)
      q = self.get_query()
      q.filter("__key__ >", entities[-1].key())
      entities = q.fetch(batch_size)

Mapper seharusnya hanya kelas abstrak yang memungkinkan Anda untuk mengulangi setiap entitas dari jenis tertentu, baik itu untuk mengekstrak datanya, atau untuk memodifikasinya dan menyimpan entitas yang diperbarui kembali ke datastore.

Jalankan dengan itu!

Sekarang, mulai konsol interaktif appengine Anda:

$python appengine_console.py <app_id_here>

Itu harus memulai konsol interaktif. Di dalamnya buat subclass Model:

from utils import Mapper
# import your model class here 
class MyModelDeleter(Mapper):
    KIND = <model_name_here>

    def map(self, entity):
        return ([], [entity])

Dan, akhirnya, jalankan (dari konsol interaktif Anda): mapper = MyModelDeleter () mapper.run ()

Itu dia!


3

Anda dapat melakukannya menggunakan antarmuka web. Masuk ke akun Anda, navigasikan dengan tautan di sisi kiri. Dalam manajemen Penyimpanan Data, Anda memiliki opsi untuk mengubah dan menghapus data. Gunakan opsi masing-masing.


3

Saya telah membuat panel add-in yang dapat digunakan dengan aplikasi App Engine yang Anda terapkan. Ini mencantumkan jenis yang ada di datastore dalam dropdown, dan Anda dapat mengklik tombol untuk menjadwalkan "tugas" yang menghapus semua entitas dari jenis tertentu atau semuanya. Anda dapat mengunduhnya di sini:
http://code.google.com/p/jobfeed/wiki/Nuke


3

Untuk Python, 1.3.8 menyertakan admin eksperimental bawaan untuk ini. Mereka mengatakan : "aktifkan bawaan berikut di file app.yaml Anda:"

builtins:
- datastore_admin: on

"Penghapusan datastore saat ini hanya tersedia dengan waktu proses Python. Namun, aplikasi Java masih dapat memanfaatkan fitur ini dengan membuat versi aplikasi Python non-default yang mengaktifkan Admin Datastore di app.yaml. Dukungan asli untuk Java akan disertakan dalam rilis mendatang. "


Menambahkan konfigurasi di app.yaml membuat kesalahan. Sebagai gantinya kita dapat mengaktifkannya dari Halaman 'Pengaturan Aplikasi' di bagian 'Administrasi'. Ada tombol untuk mengaktifkannya
Sundeep

3

Buka "Admin Datastore" untuk aplikasi Anda dan aktifkan Admin. Kemudian semua entitas Anda akan dicentang dengan kotak centang. Anda cukup memilih entri yang tidak diinginkan dan menghapusnya.


3

Inilah yang Anda cari ...

db.delete(Entry.all(keys_only=True))

Menjalankan kueri khusus kunci jauh lebih cepat daripada pengambilan penuh, dan kuota Anda akan lebih sedikit terkena karena kueri khusus kunci dianggap operasi kecil.

Berikut tautan ke jawaban dari Nick Johnson yang menjelaskannya lebih lanjut.

Di bawah ini adalah solusi REST API ujung ke ujung untuk memotong tabel ...

Saya menyiapkan REST API untuk menangani transaksi database di mana rute secara langsung dipetakan ke model / tindakan yang tepat. Ini dapat dipanggil dengan memasukkan url yang benar (example.com/inventory/truncate) dan masuk.

Berikut rutenya:

Route('/inventory/truncate', DataHandler, defaults={'_model':'Inventory', '_action':'truncate'})

Inilah penangannya:

class DataHandler(webapp2.RequestHandler):
  @basic_auth
  def delete(self, **defaults):
    model = defaults.get('_model')
    action = defaults.get('_action')
    module = __import__('api.models', fromlist=[model])
    model_instance = getattr(module, model)()
    result = getattr(model_instance, action)()

Ini dimulai dengan memuat model secara dinamis (yaitu Inventaris yang ditemukan di bawah api.models), kemudian memanggil metode yang benar (Inventory.truncate ()) seperti yang ditentukan dalam parameter tindakan.

@Basic_auth adalah dekorator / pembungkus yang menyediakan otentikasi untuk operasi sensitif (mis. POST / DELETE). Ada juga dekorator oAuth yang tersedia jika Anda mengkhawatirkan keamanan.

Akhirnya, tindakan tersebut disebut:

def truncate(self):
  db.delete(Inventory.all(keys_only=True))

Ini terlihat seperti sihir tetapi sebenarnya sangat mudah. Bagian terbaiknya adalah, delete () dapat digunakan kembali untuk menangani penghapusan satu atau banyak hasil dengan menambahkan tindakan lain ke model.


3

Anda dapat Menghapus Semua Datastore dengan menghapus semua Jenis Satu per Satu. dengan papan dasbor google appengine. Harap ikuti Langkah-langkah ini.

  1. Masuk ke https://console.cloud.google.com/datastore/settings
  2. Klik Buka Admin Datastore . (Aktifkan jika tidak diaktifkan.)
  3. Pilih semua Entitas dan tekan hapus. (Langkah ini menjalankan pekerjaan pengurangan peta untuk menghapus semua Jenis yang dipilih.)

untuk informasi lebih lanjut lihat gambar ini http://storage.googleapis.com/bnifsc/Screenshot%20from%202015-01-31%2023%3A58%3A41.png


2

Jika Anda memiliki banyak data, menggunakan antarmuka web dapat memakan waktu. The App Engine Launcher utilitas memungkinkan Anda menghapus segala sesuatu dalam satu pergi dengan 'Clear datastore pada peluncuran' kotak centang. Utilitas ini sekarang tersedia untuk Windows dan Mac (kerangka Python).


2

Untuk server pengembangan, alih-alih menjalankan server melalui peluncur mesin aplikasi google, Anda dapat menjalankannya dari terminal seperti:

dev_appserver.py --port = [portnumber] --clear_datastore = yes [nameofapplication]

mis .: aplikasi saya "reader" berjalan pada port 15080. Setelah memodifikasi kode dan memulai ulang server, saya hanya menjalankan "dev_appserver.py --port = 15080 --clear_datastore = yes reader".

Itu bagus untukku.



1

Saya sering tidak ingin menghapus semua penyimpanan data jadi saya menarik salinan bersih /war/WEB-INF/local_db.bin dari kontrol sumber. Mungkin hanya saya tetapi tampaknya bahkan dengan Mode Dev berhenti saya harus menghapus file secara fisik sebelum menariknya. Ini ada di Windows menggunakan plugin subversi untuk Eclipse.


0

Variasi PHP:

import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.DatastoreServiceFactory;

define('DATASTORE_SERVICE', DatastoreServiceFactory::getDatastoreService());

function get_all($kind) {
    $query = new Query($kind);
    $prepared = DATASTORE_SERVICE->prepare($query);
    return $prepared->asIterable();
}

function delete_all($kind, $amount = 0) {
    if ($entities = get_all($kind)) {
        $r = $t = 0;
        $delete = array();
        foreach ($entities as $entity) {
            if ($r < 500) {
                $delete[] = $entity->getKey();
            } else {
                DATASTORE_SERVICE->delete($delete);
                $delete = array();
                $r = -1;
            }
            $r++; $t++;
            if ($amount && $amount < $t) break;
        }
        if ($delete) {
            DATASTORE_SERVICE->delete($delete);
        }
    }
}

Ya itu akan memakan waktu dan 30 detik. adalah batas. Saya berpikir untuk menempatkan sampel aplikasi ajax untuk mengotomatiskan lebih dari 30 detik.


Ini bahkan bukan php yang valid. import? Mendefinisikan konstanta sebagai contoh objek?
Josh J

0
for amodel in db.Model.__subclasses__():
                dela=[]
                print amodel
                try:
                    m = amodel()
                    mq = m.all()
                    print mq.count()
                    for mw in mq:
                        dela.append(mw)
                    db.delete(dela)
            #~ print len(dela)

                except:
                    pass

0

Jika Anda menggunakan ndb, metode yang berhasil untuk saya untuk membersihkan datastore:

ndb.delete_multi(ndb.Query(default_options=ndb.QueryOptions(keys_only=True)))

1
Saya tidak berpikir ini akan berhasil. Appengine mengeluh tentang Sorry, unexpected error: The kind "__Stat_Kind__" is reserved.Ini sepertinya appengine memiliki beberapa entitas statistik internal yang dapat diekspos dengan metode ini (kemungkinan bug di ujungnya?)
menghilang

0

Untuk semua datastore yang menggunakan mesin aplikasi, bukan lokal, Anda dapat menggunakan API Datastore baru . Berikut adalah panduan untuk memulai .

Saya menulis skrip yang menghapus semua entitas non-built in. API berubah cukup cepat, jadi untuk referensi, saya mengkloningnya di commit 990ab5c7f2063e8147bcc56ee222836fd3d6e15b

from gcloud import datastore
from gcloud.datastore import SCOPE
from gcloud.datastore.connection import Connection
from gcloud.datastore import query

from oauth2client import client

def get_connection():
  client_email = 'XXXXXXXX@developer.gserviceaccount.com'
  private_key_string = open('/path/to/yourfile.p12', 'rb').read()

  svc_account_credentials = client.SignedJwtAssertionCredentials(
    service_account_name=client_email,
    private_key=private_key_string,
    scope=SCOPE)

  return Connection(credentials=svc_account_credentials)


def connect_to_dataset(dataset_id):
  connection = get_connection()
  datastore.set_default_connection(connection)
  datastore.set_default_dataset_id(dataset_id)

if __name__ == "__main__":
  connect_to_dataset(DATASET_NAME)
  gae_entity_query = query.Query()
  gae_entity_query.keys_only()
  for entity in gae_entity_query.fetch():
    if entity.kind[0] != '_':
      print entity.kind
      entity.key.delete()

0
  • melanjutkan gagasan svpino adalah bijaksana untuk menggunakan kembali catatan yang ditandai sebagai hapus. (idenya bukan untuk menghapus, tetapi tandai sebagai catatan yang tidak digunakan "dihapus"). sedikit cache / memcache untuk menangani copy pekerjaan dan hanya menulis perbedaan status (sebelum dan sesudah tugas yang diinginkan) ke datastore akan membuatnya lebih baik. untuk tugas-tugas besar dimungkinkan untuk menulis potongan perbedaan langsung ke datastore untuk menghindari kehilangan data jika memcache menghilang. untuk membuatnya loss-proof adalah mungkin untuk memeriksa integritas / keberadaan hasil memcache dan memulai kembali tugas (atau bagian yang diperlukan) untuk mengulangi perhitungan yang hilang. ketika perbedaan data ditulis ke datastore, perhitungan yang diperlukan dibuang dalam antrian.

  • Ide lain yang mirip dengan map dikurangi adalah untuk membagi jenis entitas menjadi beberapa jenis entitas yang berbeda, sehingga akan dikumpulkan bersama-sama dan terlihat sebagai jenis entitas tunggal untuk pengguna akhir. entri hanya ditandai sebagai "dihapus". ketika jumlah entri "dihapus" per pecahan mengatasi beberapa batas, entri "hidup" didistribusikan di antara pecahan lain, dan pecahan ini ditutup selamanya dan kemudian dihapus secara manual dari konsol dev (tebak dengan biaya lebih murah) upd: tampaknya tidak ada tabel drop di konsol, hanya hapus catatan-demi-catatan dengan harga reguler.

  • adalah mungkin untuk menghapus dengan query dengan chunks set record yang besar tanpa kegagalan gae (setidaknya bekerja secara lokal) dengan kemungkinan untuk melanjutkan di percobaan berikutnya ketika waktu telah habis:


    qdelete.getFetchPlan().setFetchSize(100);

    while (true)
    {
        long result = qdelete.deletePersistentAll(candidates);
        LOG.log(Level.INFO, String.format("deleted: %d", result));
        if (result <= 0)
            break;
    }
  • juga terkadang berguna untuk membuat bidang tambahan di tabel utama daripada menempatkan kandidat (rekaman terkait) ke dalam tabel terpisah. dan ya, kolom mungkin berupa array yang tidak terindeks / berseri dengan sedikit biaya komputasi.

0

Untuk semua orang yang membutuhkan solusi cepat untuk server dev (saat penulisan pada Februari 2016):

  1. Hentikan server dev.
  2. Hapus direktori target .
  3. Bangun kembali proyek.

Ini akan menghapus semua data dari datastore.


0

Saya sangat frustrasi dengan solusi yang ada untuk menghapus semua data di datastore langsung sehingga saya membuat aplikasi GAE kecil yang dapat menghapus cukup banyak data dalam waktu 30 detik.

Bagaimana menginstal dll: https://github.com/xamde/xydra


0

Untuk java

DatastoreService db = DatastoreServiceFactory.getDatastoreService();
List<Key> keys = new ArrayList<Key>();
for(Entity e : db.prepare(new Query().setKeysOnly()).asIterable())
    keys.add(e.getKey());
db.delete(keys);

Bekerja dengan baik di Server Pengembangan


Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.